基准测试和分析之间的区别

Difference between Benchmarking and Profiling

我看到术语 软件基准测试分析 有时可以互换使用,但据我所知,两者之间存在细微差别。

两者在时间上是相连的。但是,虽然基准测试主要是确定可以与其他应用程序进行比较的特定速度分数,但分析可以为您提供有关应用程序大部分时间(或周期数)的确切信息。

对我来说,它总是这样的:集成测试是基准测试的对应物,单元测试是分析的对应物。但是微基准测试如何适应这一点呢?

有人说 here:

Profiling and benchmarking are flip sides of the same coin, profiling helps you to narrow down to where optimization would be most useful, benchmarking allows you to easily isolate optimizations and cross-compare them.

另一个人说 here 关于分析:

Profiling means different things at different times. Sometimes it means measuring performance. Sometimes it means diagnosing memory leaks. Sometimes it means getting visibility into multi-threading or other low-level activities.

那么,这些技术在概念上是不同的还是不是那么黑白分明?

A benchmark can help you observe the system's behavior under load, determine the system's capacity, learn which changes are important, or see how your application performs with different data.

Profiling is the primary means of measuring and analyzing where time is consumed. Profiling entails two steps: measuring tasks and the time elapsed, and aggregating and sorting the results so that the important tasks bubble to the top. --High performance MySQL

我的理解是:benchmark 是了解您的应用程序的措施,而 profiling 是改进您的应用程序的措施。

基准是衡量整个操作的时间的东西。例如在某些工作负载下每秒 I/O 次操作。因此 结果通常是一个数字,以秒或每秒操作数为单位 。或具有不同参数结果的数据集,因此您可以绘制它。

您可以使用基准来比较不同硬件上的相同软件,或者与您的基准交互的某些其他软件的不同版本。例如使用不同的 Apache 设置对每秒最大连接数进行基准测试。


分析的目的不是比较不同的事物:它是关于了解程序的行为。 配置文件结果可能是 table 每个函数花费的时间 even per instruction with a sampling profiler。您可以说它是配置文件而不是基准测试,因为说“该功能花费的时间最少,因此我们将保留该功能并停止使用其余功能”是没有意义的。

阅读维基百科文章以了解更多信息:https://en.wikipedia.org/wiki/Profiling_(computer_programming)

您使用配置文件找出要优化的地方。在您的程序花费 99% 的时间的函数中实现 10% 的加速比在任何其他函数中实现 100% 的加速更有价值。更好的是,当你可以改进你的高级设计时,昂贵的函数被调用的次数更少,而且速度更快。


微基准测试 是一种特定形式的基准测试。这意味着您正在测试一个非常具体的事物,以单独衡量它,而不是任何真正有用的事物的整体性能。

示例微基准测试结果:

  • Intel Haswell's L1 cache load-use latency is 4 cycles.

  • 这个版本的memcpy达到其他版本吞吐量的80%

  • 。 (mov-elimination 仅适用于 Intel 的不同寄存器之间)。请参阅 link 获取静态执行程序的完整 asm 源代码table,性能计数器结果来自 运行 将其与几个不同的循环体结合以演示移动消除。

    使用 CPU 性能计数器来衡量微基准 运行s 是进行实验以了解 CPUs 内部工作方式的好方法。有关更多示例,请参见 。在那种情况下,您正在分析您的微基准以了解是什么使它达到 运行 的速度。 (通常您对 uops_executed 之类的性能计数器比对实际时间或时钟周期计数更感兴趣,例如测试微融合/未层压而不需要在实际影响的地方进行循环每次迭代的周期数。)

示例非微基准测试结果:

  • 使用 7-zip(具有特定选项和硬件)压缩这个 100MB 的文件集合需要 23 秒。
  • 在某些硬件/软件组合上编译 Linux 内核需要 99 秒。

另见 https://en.wikipedia.org/wiki/Benchmark_(computing)#Types_of_benchmarks

微基准测试是基准测试的一个特例。如果你做对了,它会告诉你哪些操作很昂贵,哪些操作很便宜,这在你尝试优化时会有所帮助。如果你做错了,你可能根本没有衡量你打算衡量的东西。例如您编写了一些 C 语言来测试 for 循环与 while 循环,但是编译器出于不同的原因生成了不同的代码,您的结果毫无意义。 (表达相同逻辑的不同方式对于现代优化编译器几乎无关紧要;不要在这上面浪费时间。)微基准测试很难。

另一种表明它是微基准测试的方法是,您通常需要查看编译器的 asm 输出以确保它正在测试您希望它测试的内容。 (例如,它没有通过从循环中提升一些昂贵的东西来优化你的重复 10M 次循环的迭代,这些循环应该重复整个操作足够的时间来提供可以准确测量的持续时间。)

微基准测试可能会扭曲事物,因为它们使用热缓存和分支预测器来测试您的函数,并且它们不会 运行 在被测代码调用之间的任何其他代码。这可以使巨大的循环展开看起来不错,但作为真实程序的一部分,它会导致更多的缓存未命中。同样,它使大查找 table 看起来不错,因为整个查找 table 最终都在缓存中。完整的程序通常会在函数调用之间弄脏足够的缓存,以至于查找 table 并不总是在缓存中命中,因此仅计算一些东西会更便宜。 (大多数程序都是受内存限制的。重新计算不太复杂的东西通常和查找它一样快。)

通常人们进行性能分析不是为了衡量一个程序有多快,而是为了找出如何让它更快。

通常他们这样做是基于这样的假设,即通过测量特定函数或代码行所花费的时间来最好地发现速度慢。

有一种清晰的思考方式:如果一个函数或一行代码显示 包含百分比 的时间,即如果可以使函数或代码行花费零时间(通过不执行它或将其传递给无限快的处理器)。

除了函数或代码行之外,还有其他事情可能需要时间。 这些是程序正在做什么的描述,但它们不是唯一的描述。

假设您 运行 一个分析器,每 N 秒的实际时间(不仅仅是 CPU 时间)收集程序状态的样本,包括调用堆栈和数据变量。 调用栈不仅仅是一堆函数名称——它是一堆调用这些函数的调用点,通常是参数值。 那么假设您可以检查并描述其中的每一个。

例如,样本的描述可以是:

  • 例程 X 正在分配内存,以便在必要时初始化例程 Q 用于记录患者的字典。

  • 该程序正在读取一个 dll 文件以提取一个字符串资源,在调用堆栈的几个级别上,将用于填充进度条中的文本字段它的存在是为了告诉用户为什么程序要花这么长时间:)

  • 程序正在使用某些参数调用函数 F,并且它之前使用相同的参数调用它,给出相同的结果。这表明人们可能只记得先前的结果。

  • 程序正在调用函数 G,而函数 G 正在调用函数 H,只是为了破译 G 的参数选项标志。程序员知道这些标志总是相同的,建议使用特殊版本的 G 可以节省时间。

  • 等。等等

这些是可能的描述。 如果它们占时间的 F 百分比,那就是每个样本满足该描述的概率。 更简单的描述是:

  • 例程或代码行 X 出现在 Q% 的堆栈样本中。即 测量的包含百分比
  • 例程 D 在堆栈样本的 R 百分比上出现在例程 E 的正上方。该数字可以放在从 D 到 E 的调用图的弧上。
  • 栈序列main->A->B->C->D->E是样本出现次数最多的序列。即"hot path".
  • 最常出现在栈底的例程是T。也就是"hot spot"。

大多数分析器工具只为您提供这些简单的描述。 一些程序员了解自己检查示例的价值,因此他们可以对程序花费时间的原因进行更多语义描述。 如果 objective 要准确测量由于特定描述而导致的时间百分比,则必须检查大量样本。 但是,如果描述出现在样本的很大一部分上,则没有准确测量它,但知道它很大,并且已 准确找到 。 看到不同? 您可以用测量的准确性来换取加速发现的能力。

这就是random pausing, and the statistical justification is here背后的原理。

配置文件示例

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

输出

    197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 re.py:212(compile)
     1    0.000    0.000    0.001    0.001 re.py:268(_compile)
     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)
     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)
     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)
   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)

https://docs.python.org/3.10/library/profile.html?highlight=profile#module-profile

The profiler modules are designed to provide an execution profile for a given program,

for benchmarking purposes, there is timeit for reasonably accurate results.

This particularly applies to benchmarking Python code against C code: the profilers introduce overhead for Python code, but not for C-level functions (unfair!), and so the C code would seem faster than any Python one.

cProfile is recommended for most users; it’s a C extension with reasonable overhead that makes it suitable for profiling long-running programs. Based on lsprof