查看源码 基准测试

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

使用 erlperf

一个有用的基准测试工具是 erlperf文档)。它使查找哪个代码更快变得简单。例如,以下是如何比较两种生成随机字节的方法

% erlperf 'rand:bytes(2).' 'crypto:strong_rand_bytes(2).'
Code                                 ||        QPS       Time   Rel
rand:bytes(2).                        1    7784 Ki     128 ns  100%
crypto:strong_rand_bytes(2).          1    2286 Ki     437 ns   29%

Time 列,我们可以读出平均调用 rand:bytes(2) 执行时间为 128 纳秒,而调用 crypto:strong_rand_bytes(2) 执行时间为 437 纳秒。

QPS 列,我们可以读出每秒可以进行的调用次数。对于 rand:bytes(2),它为每秒 7,784,000 次调用。

Rel 列显示相对差异,100% 表示最快的代码。

当一次生成两个随机字节时,rand:bytes/1crypto:strong_rand_bytes/1 快三倍以上。假设我们真的需要强随机数,并且需要尽可能快地获得它们,我们该怎么办?一种方法是一次生成多于两个字节。

% erlperf 'rand:bytes(100).' 'crypto:strong_rand_bytes(100).'
Code                                   ||        QPS       Time   Rel
rand:bytes(100).                        1    2124 Ki     470 ns  100%
crypto:strong_rand_bytes(100).          1    1915 Ki     522 ns   90%

当我们一次生成 100 个字节时,rand:bytes/1 仍然更快,但相对差异较小。

% erlperf 'rand:bytes(1000).' 'crypto:strong_rand_bytes(1000).'
Code                                    ||        QPS       Time   Rel
crypto:strong_rand_bytes(1000).          1    1518 Ki     658 ns  100%
rand:bytes(1000).                        1     284 Ki    3521 ns   19%

当我们一次生成 1000 个字节时,crypto:strong_rand_bytes/1 现在是最快的。

使用 Erlang/OTP 功能进行基准测试

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

  • timer:tc/3 测量挂钟时间。挂钟时间的优点是 I/O、交换以及操作系统内核中的其他活动都包含在测量中。缺点是测量值通常变化很大。通常最好多次运行基准测试并记录最短时间,这是在最佳情况下可能实现的最短时间。

  • statistics(runtime) 测量在 Erlang 虚拟机中花费的 CPU 时间。CPU 时间的优点是结果在每次运行中更加一致。缺点是不包括在操作系统内核中花费的时间(例如交换和 I/O)。因此,如果涉及任何 I/O(文件或套接字),则测量 CPU 时间会产生误导。

进行挂钟测量和 CPU 时间测量可能都是一个好主意。

一些最后的建议

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