【发布时间】:2009-02-03 01:33:03
【问题描述】:
我知道我必须使用:rdtsc。测量的函数是确定性的,但结果远非可重复(从运行到运行我得到 5% 的振荡)。 可能的原因有:
- 上下文切换
- 缓存未命中
你知道其他原因吗? 如何消除它们?
【问题讨论】:
我知道我必须使用:rdtsc。测量的函数是确定性的,但结果远非可重复(从运行到运行我得到 5% 的振荡)。 可能的原因有:
你知道其他原因吗? 如何消除它们?
【问题讨论】:
TSC(rdtsc 使用的)通常在多处理器系统上不同步。为了将进程绑定到单个 CPU,设置 CPU 亲和性可能会有所帮助。
如果可用,您还可以从HPET timers 获取时间戳,这不会出现同样的问题。
至于可重复性,这些差异是真实的。您可以禁用缓存,为进程提供实时优先级和/或(如果在 Linux 或类似的东西上)使用较低的固定定时器中断频率(执行时间片的中断频率)重新编译内核。您无法完全消除差异,至少在常规 CPU + 操作系统组合上不会轻易消除。
一般来说,出于易于编码、可靠性和可移植性的原因,我建议您使用操作系统提供的功能。如果它提供高精度计时器,请使用适当的操作系统助手。
(以防万一您尝试对加密系统进行时间攻击,那么您将不得不忍受 1. 这种随机性和 2. 出于充分的理由使系统不可预测的一般防御,所以函数对于时间可能不是确定性的。)
编辑:添加了有关操作系统可以提供的计时器的段落。
编辑:这是指 Linux。要将进程绑定到单个 CPU(从 RDTSC 中准确读取),您可以使用 sched_setaffinity(2)。 here 的一些代码来自我的一个项目,将其用于其他目的(将线程映射到 CPU)。这应该是你的第一次尝试。至于 HPET,只要内核和机器支持这些计时器,您就可以使用常规的 POSIX 调用,例如 these。
【讨论】:
为什么要消除它们?听起来您已经创建了一个现实的基准。该代码在野外使用时也会具有相同的可变性。可能更糟,因为您可能已经消除了磁盘和 CPU 缓存延迟。使用 Jon Skeet 的方法,为您创造可能的最佳结果的条件,只会给您留下让您感觉良好但永远无法实现的结果。
如果绝对数字很重要,请计算中位数,而不是平均值。
【讨论】:
请参阅问题Is stopwatch benchmarking acceptable?,了解现代多核多线程多进程机器上微基准测试的差异。
虽然问题是关于 Java 的,但这些注意事项适用于任何语言的基准测试。
另见:How do I write a correct micro-benchmark in Java?
另见:What advice can you give me for writing a meaningful benchmark?
【讨论】:
实际上在较新的 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 (Performance API) toolkit,然后,在某些(尽管不是全部)操作系统上安装一个内核补丁,并且通过一些额外的努力,您重新关闭并运行。
【讨论】: