基准测试CPU密集型算法/实现

4
假设我正在用一种编译语言(例如C ++)编写自己的StringBuilder,那么如何度量各种实现的性能最佳?仅计时数十万次运行会产生高度不一致的结果:从一个批次到另一个批次的计时可能相差多达15%,因此无法准确评估可产生小于该值的性能增益的潜在性能改进。我已经执行了以下步骤:
1. 关闭SpeedStep 2. 使用RDTSC进行计时 3. 以实时优先级运行进程 4. 将亲和力设置为单个CPU核心
这使结果变得更稳定。您还有其他想法吗?

2
尝试计时超过几十万次运行。每个基准测试至少运行10秒钟。 - Mysticial
1
你似乎正在做所有正确的事情。虽然这并没有回答你的问题,但我发现一个有用的方法是始终在固定的时间内运行测试,并计算在该时间间隔内完成了多少操作,而不是运行固定数量的操作并计算它们所需的时间。 - Mike Nakis
迈克,那样做有什么好处吗?你只是因为这样可以得到更一致的结果吗? - Vladimir Panteleev
不,我没有,这就是为什么我说它没有提供你问题的答案。它所做的只是保证测试运行的时间,这样我就知道是否有时间去冲杯咖啡,看下一集《生活大爆炸》等等。 - Mike Nakis
新一代的CPU拥有“恒定速率TSC”,这意味着rdtsc返回的计数器更接近经过的时间,而不是周期计数。我不确定在Core i7上禁用SpeedStep是否足以抵消这种影响。 - Vladimir Panteleev
显示剩余5条评论
3个回答

7

我以如下方式实现了100%的一致结果:

  1. 使用MS-DOS设置Bochs。
  2. 设置您的工具链以针对MS-DOS
    — 或者 —
    1. 将您的工具链设置为针对32位Windows
    2. 在Bochs中安装HX-DOS extender
    3. 如果需要,请修改您的工具包标准库/运行时,并删除需要在HX-DOS中未实现的Windows API的功能。在尝试运行程序时,扩展器将打印未实现API的列表。
  3. 将基准测试中的周期数减少几个数量级。
  4. 用汇编指令cli / sti包装基准测试代码(请注意,在此更改后,二进制文件将无法在现代操作系统上运行)。
  5. 如果您还没有,使您的基准测试使用rdtsc差值进行计时。样本应在clisti指令内。
  6. 在Bochs中运行它!

Bochs screenshot

结果似乎完全是确定性的,但并不是对整体性能的准确评估(有关详细信息,请参见 Osman Turan 答案下的讨论)。
作为额外提示,以下是与Bochs共享文件的简单方法(这样您就不必每次卸载/重建/重新安装软盘映像):
在Windows上,Bochs将锁定软盘映像文件,但该文件仍以共享写模式打开。这意味着您无法覆盖该文件,但可以向其中写入内容。(我认为*nix操作系统可能会导致覆盖创建一个新文件,就文件描述符而言。)诀窍是使用dd。我设置了以下批处理脚本:
... benchmark build commands here ...
copy /Y C:\Path\To\Benchmark\Project\test2dos.exe floppy\test2.exe
bfi -t=288 -f=floppysrc.img floppy
dd if=floppysrc.img of=floppy.img

bfi 是Bart的制作软盘镜像工具。

然后,在Bochs中挂载floppy.img即可。


奖励小贴士#2:为了避免在Bochs中每次手动启动基准测试,可以在软盘目录中放置一个空的go.txt文件,并在Bochs中运行此批处理文件:

@echo off
A:
:loop
choice /T:y,1 > nul
if not exist go.txt goto loop
del go.txt
echo ---------------------------------------------------
test2
goto loop

每当检测到新的软盘映像,它都会启动测试程序。这样,您可以在一个脚本中自动化基准测试运行。
更新:该方法并不是非常可靠。有时候,只要重新排序一些测试,时间会变化多达200%(当使用原始问题中描述的方法在真实硬件上运行时,这些时间变化并未观察到)。

酷!C-:= 很遗憾,为了这项工作必须使用整个虚拟机,因为一个单独的自包含386代码的解释器就足够进行基准测试。如果只能找到这样的东西就好了! - Mike Nakis
为什么建议使用MS-DOS?您可能想在您的bochs.rc中设置时钟:sync=slowdown。 - Matthias Weiler
嗯,因为MS-DOS的开销非常小,而且没有抢占式多任务处理。 - Vladimir Panteleev

1

我过去一直非常关注这个问题,现在我已经意识到只有一个理想的解决方案,虽然需要大量的工作(主要是准备工作),所以我从来没有真正按照这种方式做过。

解决方案是使用386模拟器运行您的代码,它将告诉您执行了多少个操作。您应该能够找到一个开源的386模拟器。它将准确到指令,并且只需要运行一次测试。如果您这样做了,请发布您的做法!


1
哈哈,我正在设置BOCHS,就在你发布这个帖子的时候:) 的确,BOCHS的rdtsc指令返回虚拟计数器,所以我认为它会更准确。 - Vladimir Panteleev
我做到了!请看下面的答案。 - Vladimir Panteleev

1

精确地测量一段代码确实很困难。针对这样的需求,我建议您查看Agner Fog's测试套件。使用它,您可以测量时钟周期并收集一些重要因素(例如缓存未命中,分支预测错误等)。

此外,我建议您查看Agner网站的PDF文档。这是一份非常宝贵的文件,使微优化成为可能。

顺便说一句,实际性能不是“时钟周期”的函数。缓存未命中可能会改变每次运行真实应用程序的结果。因此,我会先优化缓存未命中。仅仅对同一内存部分的代码块运行几次就可以大大减少缓存未命中。所以,这使得精确测量变得困难。整个应用程序调优通常是更好的选择。 Intel VTune和其他工具非常适合这样的用途。


1
我相信所有的模拟器都高度模拟了顺序CPU,因此它们甚至不使用RDTSC来反映实际性能。我相信作者为每个指令分配了一些固定的时钟周期并对其进行计数。因此,这更像是计算与实际性能无关的某些临时固定数字。请注意,今天的CPU有几条流水线,性能函数有点更加复杂。 - Osman Turan
毫无疑问,时钟计数 - 无论是在真实的还是模拟的CPU上 - 并不表示实际性能,但它们在多次运行中高度一致的潜力确实使它们以自己的方式变得有用。 - Vladimir Panteleev
我同意。但是,计算一些由模拟器作者确定的临时数字也不反映任何东西。至于一致性,这并不奇怪,因为它们只是模拟器内部的一些确定性数字。我认为,Agnor的测试套件确实是您想要的。 - Osman Turan
Agner Fog的PMCTest似乎产生非常一致的结果。例如,“Instruct”计数器的误差小于0.01%。玩弄Bochs很有趣,但我可能早该尝试这个了 :) - Vladimir Panteleev
我告诉过你,这真的是你想要的 ;) - Osman Turan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接