12  分析

12 分析

即使是经验丰富的软件开发人员,也经常会错误地猜测程序中性能瓶颈所在。因此,分析您的程序以查看性能瓶颈在哪里,并专注于优化它们。

Erlang/OTP 包含多个工具来帮助查找瓶颈

  • fprof 提供了有关程序时间消耗的最详细的信息,但它会显著降低被分析程序的速度。

  • eprof 提供程序中使用的每个函数的时间信息。不会生成调用图,但 eprof 对其分析的程序的影响要小得多。

    如果程序太大而无法被 fprofeprof 分析,可以使用 cprof 来定位需要使用 fprofeprof 进行更彻底分析的代码部分。

  • cprof 是最轻量级的工具,但它只提供基于函数的执行计数(针对所有进程,而不是每个进程)。

  • dbg 是通用的 Erlang 跟踪前端。通过使用 timestampcpu_timestamp 选项,它可以用于计时实时系统中的函数调用需要多长时间。

  • lcnt 用于查找 Erlang 运行时系统内部锁定机制中的争用点。当查找进程、端口、ets 表和其他可以在并行运行的实体之间的交互瓶颈时,它非常有用。

这些工具将在 工具 中进一步描述。

还有一些 Erlang/OTP 之外的开源工具可用于帮助分析。其中一些是

  • erlgrind 可用于在 kcachegrind 中可视化 fprof 数据。
  • eflame 是 fprof 的替代品,它将分析输出显示为火焰图。
  • recon 是一个 Erlang 分析和调试工具集合。该工具附带一本名为 Erlang in Anger 的电子书。
  • perf 是一个针对 Linux 的采样分析器,它提供类似于 fprof 的功能,但开销要低得多。当模拟器使用 +JPperf true 模拟器标志启动时,可以分析 Erlang 代码,并且只有在启用 JIT 时才可用。

    有关如何运行 perf 的更多详细信息,请参阅 BeamAsm 内部文档中的 perf 支持 部分。

eheap_alloc: Cannot allocate 1234567890 bytes of memory (of type "heap").

以上口号是 Erlang 终止的更常见原因之一。由于未知原因,Erlang 运行时系统无法分配内存以供使用。发生这种情况时,将生成一个崩溃转储,其中包含有关系统在内存不足时运行状态的信息。使用 crashdump_viewer 来查看正在使用的内存。查找堆很大的进程或消息很多、ets 表很大等。

在查看正在运行的系统中的内存使用情况时,最基本的函数是 erlang:memory()。它返回系统的当前内存使用情况。可以使用 instrument(3) 获得对内存使用情况的更详细的细分。

然后可以使用相应的 info 函数检查进程、端口和 ets 表,例如 erlang:process_info/2 erlang:port_info/2 ets:info/1

有时,系统可能会进入一种状态,其中 erlang:memory(total) 报告的内存与操作系统报告的内存大不相同。这是因为 Erlang 运行时系统内部存在碎片。可以使用 erlang:system_info(allocator) 获取有关内存分配方式的数据。从该函数获取的数据非常原始,阅读起来不太愉快。可以使用 recon_alloc 从 system_info 统计计数器中提取有用的信息。

对于大型系统,从模拟和有限的场景开始运行分析可能很有趣。但瓶颈往往只会在同时发生很多事情时以及涉及很多节点时才会出现或导致问题。因此,还希望在真实目标系统上的系统测试工厂中运行分析。

对于大型系统,您不希望在整个系统上运行分析工具。相反,您希望专注于核心进程和模块,它们占执行时间的很大一部分。

还有一些工具可用于以或多或少的开销查看整个系统。

  • observer 是一个 GUI 工具,它可以连接到远程节点并显示有关正在运行系统的各种信息。
  • etop 是一个命令行工具,它可以连接到远程节点并显示类似于 UNIX 工具 top 显示的信息。
  • msacc 允许用户查看 Erlang 运行时系统花费时间做什么。开销非常低,这使得它在负载很重的系统中运行很有用,以便了解从哪里开始进行更细粒度的分析。

在分析分析活动的输出文件时,请查找调用次数很多且“自身”执行时间很长的函数(不包括对其他函数的调用的时间)。调用次数很多的功能也很有趣,因为即使是很小的东西,如果重复很多次,也会加起来很多。还要问问自己,您可以做些什么来减少这段时间。以下是一些适合您问自己的问题

  • 是否可以减少函数的调用次数?
  • 如果更改测试顺序,是否可以减少任何测试的运行频率?
  • 是否可以删除任何冗余测试?
  • 任何计算表达式是否每次都给出相同的结果?
  • 是否有其他等效且更高效的方法来执行此操作?
  • 是否可以使用其他内部数据表示来提高效率?

这些问题并不总是容易回答。可能需要一些基准测试来支持您的理论,并避免在您的理论错误时使事情变得更慢。有关详细信息,请参阅 基准测试

fprof 测量每个函数的执行时间,包括自身时间(即函数自身执行使用的时间)和累计时间(即包括调用的函数的时间)。这些值按进程显示。您还可以了解每个函数被调用的次数。

fprof 基于跟踪到文件,以最大程度地减少运行时性能影响。使用 fprof 只需要调用几个库函数,请参阅工具中的 fprof 手册页。

eprof 基于 Erlang 的 trace_info BIF。 eprof 显示每个进程使用了多少时间以及这些时间花费在哪些函数调用中。时间显示为总时间的百分比和绝对时间。有关更多信息,请参阅工具中的 eprof 手册页。

cprof 在功能方面介于 fprofcover 之间。它统计程序运行时每个函数被调用的次数,按模块为单位。 cprof 的性能下降影响很小(与 fprof 相比)并且不需要重新编译任何模块进行分析(与 cover 相比)。有关更多信息,请参阅工具中的 cprof 手册页。

工具 结果 结果大小 对程序执行时间的影响 记录调用次数 记录执行时间 记录被谁调用 记录垃圾回收
fprof 每个进程到屏幕/文件 显著减速 总计和自身
eprof 每个进程/函数到屏幕/文件 轻微减速 仅总计
cprof 每个模块到调用者 轻微减速

表 12.1:工具摘要

dbg 是一个通用的 Erlang 跟踪工具。通过使用 timestampcpu_timestamp 选项,它可以用作精确的仪器来分析特定进程的函数调用需要多长时间。这在尝试了解时间在负载很重的系统中花费在何处时非常有用,因为它可以将分析范围限制在非常小的范围内。有关更多信息,请参阅运行时工具中的 dbg 手册页。

lcnt 用于分析并行运行的实体之间的交互。例如,如果您有一个进程,系统中的所有其他进程都需要与其交互(也许它有一些全局配置),那么 lcnt 可用于确定与该进程的交互是否是一个问题。

在 Erlang 运行时系统中,实体仅在有多个调度程序时才并行运行。因此,lcnt 将在使用多个核心上的多个调度程序的系统上显示更多争用点(因此更有用)。

有关更多信息,请参阅工具中的 lcnt 手册页。

基准测试的主要目的是找出给定算法或函数的哪种实现最快。基准测试远非精确的科学。当今的操作系统通常会运行难以关闭的后台任务。缓存和多个 CPU 内核不利于基准测试。最好在基准测试时在单用户模式下运行 UNIX 计算机,但这对于非正式测试来说至少是不方便的。

基准测试可以测量挂钟时间或 CPU 时间。

  • timer:tc/3 测量挂钟时间。挂钟时间的优点是 I/O、交换和其他操作系统内核活动包含在测量中。缺点是测量结果差异很大。通常,最好运行基准测试几次并记录最短时间,这将是最佳情况下可以实现的最小时间。
  • statistics/1 带有参数 runtime 测量 Erlang 虚拟机中花费的 CPU 时间。CPU 时间的优点是结果在每次运行之间更加一致。缺点是未包含操作系统内核中花费的时间(例如交换和 I/O)。因此,如果涉及任何 I/O(文件或套接字),测量 CPU 时间就会产生误导。

最好同时进行挂钟测量和 CPU 时间测量。

一些最终建议

  • 两种测量类型的粒度都可能很高。因此,请确保每次单独测量至少持续几秒钟。
  • 为了使测试公平,每次新的测试运行都将在其自己的新创建的 Erlang 进程中运行。否则,如果所有测试都在同一个进程中运行,则后面的测试将以更大的堆大小开始,因此可能执行更少的垃圾收集。还要考虑在每次测试之间重新启动 Erlang 模拟器。
  • 不要假设给定算法在计算机体系结构 X 上的最快实现也是在计算机体系结构 Y 上最快的。