如何准确测量c ++函数使用的时钟周期?

时间:2009-02-03 01:33:04

标签: performance benchmarking

我知道我必须使用:rdtsc。测量的函数是确定性的,但结果远非可重复(我从运行到运行得到5%的振荡)。 可能的原因是:

  • 上下文切换
  • 缓存未命中

你知道其他原因吗? 如何消除它们?

6 个答案:

答案 0 :(得分:5)

TSC( rdtsc 使用的)通常在多处理器系统上不同步。设置CPU亲和性以将进程绑定到单个CPU可能会有所帮助。

您还可以从HPET timers获取时间戳(如果可用),这些时间戳不会出现同样的问题。

至于重复性,这些差异是正确的。您可以禁用缓存,为进程提供实时优先级和/或(如果在Linux或类似的情况下)使用较低的固定定时器中断频率(执行时间分片的那个)重新编译内核。你不能完全消除差异,至少不容易消除,而不是常规的CPU + OS组合。

一般而言,为了便于编码,可靠性和便携性,我建议您使用操作系统提供的功能。如果它提供高精度计时器,请使用适当的OS助手。

(以防万一你正在尝试对加密系统进行时间攻击,好吧,你将不得不忍受1.这种随机性和2.一般防御使得系统因各种原因无法预测,所以关于时间,函数可能不是确定性的。)

编辑:添加了关于操作系统可以提供的计时器的段落。

编辑:这是指Linux。要将进程绑定到单个CPU(从RDTSC获得准确的读取),可以使用sched_setaffinity(2)。而here是我的一个项目中的一些代码,它们将其用于其他目的(将线程映射到CPU)。这应该是你的第一次尝试。对于HPET,只要内核和机器支持这些定时器,就可以使用these之类的常规POSIX调用。

答案 1 :(得分:2)

请参阅问题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?

答案 2 :(得分:2)

为什么要消除它们?听起来你已经创造了一个现实的基准。在野外使用时,该代码也具有相同的可变性。可能更糟,因为您可能已经消除了磁盘和CPU缓存延迟。使用Jon Skeet的方法,创造条件,给你最好的结果,只会给你一个让你感觉良好但永远无法实现的结果。

如果绝对数字很重要,请计算中位数,而不是平均数。

答案 3 :(得分:2)

实际上在较新的Linux内核中有新的perf子系统。例如:

$ ./perf stat du -s /tmp
94796   /tmp

 Performance counter stats for 'du -s /tmp':

          2.546403 task-clock-msecs         #      0.060 CPUs 
                 3 context-switches         #      0.001 M/sec
                 0 CPU-migrations           #      0.000 M/sec
               166 page-faults              #      0.065 M/sec
           2434963 cycles                   #    956.236 M/sec
           1798092 instructions             #      0.738 IPC  
            302969 branches                 #    118.979 M/sec
             26197 branch-misses            #      8.647 %    
             23217 cache-references         #      9.118 M/sec
              4621 cache-misses             #      1.815 M/sec

        0.042406580  seconds time elapsed

答案 4 :(得分:1)

添加到原因列表:分支预测/误预测(这可以通过在某些芯片上具有复杂预测缓存的上下文切换来实现。此外,预测可能受到程序的不同输入的影响,因此直接比较时间两个不同的数据集可能略有偏差。

一般来说,几乎不可能减轻所有这些,但是你可以做些事情来帮助每一个:

  • 缓存未命中:在开始计时之前“填充”缓存。不要忘记还有一个指令缓存,也需要准备好。对于小型数据集,只需在没有定时的情况下运行整个测试一次,然后再按时间运行。对于大型数据集,请执行此操作,然后使用处理器的预先缓存指令将第一个数据块加载回缓存。
  • 上下文切换:在负载较轻的系统上使用多处理器/核心芯片,并将进程的亲和性设置为特定的CPU(最好不是CPU 0)。这也有助于缓存未命中(因为移动CPU意味着缓存完全丢失)和分支预测(因为它实际上是一种缓存形式)。

但是,当然,到目前为止,做这样的计时的最佳方法是在非常大的数据上多次执行这些操作,以便最大限度地减少由您无法控制的事物引入的可变性。 (它永远不会被删除。)

答案 5 :(得分:0)

大多数现代处理器都支持一组卓越的低级硬件性能计数器。 如果您真的想知道答案,包括对缓存未命中和上下文切换开销的实际测量,请抓住PAPI (Performance API) toolkit,然后,在某些(尽管不是全部)操作系统上安装一个内核补丁,并且需要额外的努力,你已经开始跑步了。