我知道我必须使用:rdtsc。测量的函数是确定性的,但结果远非可重复(从运行到运行我得到 5% 的振荡)。可能的原因有:
- 上下文切换
- 缓存未命中
你知道其他原因吗?如何消除它们?
我知道我必须使用:rdtsc。测量的函数是确定性的,但结果远非可重复(从运行到运行我得到 5% 的振荡)。可能的原因有:
你知道其他原因吗?如何消除它们?
TSC(rdtsc使用的)通常在多处理器系统上不同步。设置 CPU 亲和性以将进程绑定到单个 CPU 可能会有所帮助。
如果可用,您还可以从HPET 计时器获取时间戳,这不容易出现同样的问题。
至于可重复性,这些差异是真实的。您可以禁用缓存,为进程提供实时优先级和/或(如果在 Linux 或类似的东西上)使用较低的固定定时器中断频率(执行时间片的中断频率)重新编译内核。您不能完全消除差异,至少不容易,也不能在常规 CPU + OS 组合上。
一般来说,出于易于编码、可靠性和可移植性的原因,我建议您使用操作系统提供的功能。如果它提供高精度计时器,请使用适当的操作系统助手。
(以防万一您尝试对加密系统进行时间攻击,那么您将不得不忍受 1. 这种随机性和 2. 出于充分理由使系统不可预测的一般防御措施,因此该功能可能不会对于时间是确定性的。)
编辑:添加了有关操作系统可以提供的计时器的段落。
编辑:这是指 Linux。要将进程绑定到单个 CPU(从 RDTSC 中准确读取),您可以使用sched_setaffinity(2)。这是我的一个项目中的一些代码,将其用于其他目的(将线程映射到 CPU)。这应该是你的第一次尝试。至于 HPET,只要内核和机器支持这些计时器,您就可以使用像这样的常规 POSIX 调用。
为什么要消灭它们?听起来您已经创建了一个现实的基准。该代码在野外使用时也会具有相同的可变性。可能更糟,因为您可能已经消除了磁盘和 CPU 缓存延迟。使用 Jon Skeet 的方法,创造条件给你最好的结果,只会给你留下一个让你感觉良好但永远无法实现的结果。
如果绝对数字很重要,请计算中位数,而不是平均值。
请参阅问题秒表基准测试是否可以接受?关于现代多核多线程多进程机器上的微基准测试差异的论述。
尽管问题是关于 Java 的,但考虑因素适用于任何语言的基准测试。
另请参阅:对于编写有意义的基准,您能给我什么建议?
实际上,较新的 Linux 内核中有新的 perf 子系统。例子:
$ ./perf stat du -s /tmp 94796 /tmp 'du -s /tmp' 的性能计数器统计信息: 2.546403 任务时钟毫秒 #0.060 CPU 3 个上下文切换 # 0.001 M/sec 0 CPU 迁移 # 0.000 M/sec 166 个页面错误 # 0.065 M/sec 2434963 次循环 #956.236 M/秒 1798092 指令#0.738 IPC 302969 个分支 # 118.979 M/sec 26197 分支未命中 # 8.647 % 23217 缓存引用 # 9.118 M/秒 4621 缓存未命中 # 1.815 M/秒 0.042406580 秒经过的时间
添加到原因列表:分支预测/错误预测(这可能会受到某些芯片上具有复杂预测缓存的上下文切换的影响。预测也会受到程序的不同输入的影响,因此直接比较两个不同数据的时间集合可能会稍微倾斜。
一般来说,要减轻所有这些几乎是不可能的,但是您可以做一些事情来帮助每个人:
但是,当然,到目前为止,进行此类计时的最佳方法是对非常大的数据进行多次计时,以便将您无法控制的事物引入的可变性降至最低。(它永远无法真正被抹去。)
大多数现代处理器都支持一组非凡的低级硬件性能计数器。如果您真的想知道答案,包括对缓存未命中和上下文切换开销的实际测量,请获取PAPI(性能 API)工具包,然后,在某些(尽管不是全部)操作系统上安装一个内核补丁,并付出一些额外的努力,你已经开始跑步了。