【问题标题】:How to accurately measure clock cycles used by a c++ function?如何准确测量 c++ 函数使用的时钟周期?
【发布时间】:2009-02-03 01:33:03
【问题描述】:

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

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

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

【问题讨论】:

    标签: performance benchmarking


    【解决方案1】:

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

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

    至于可重复性,这些差异是真实的。您可以禁用缓存,为进程提供实时优先级和/或(如果在 Linux 或类似的东西上)使用较低的固定定时器中断频率(执行时间片的中断频率)重新编译内核。您无法完全消除差异,至少在常规 CPU + 操作系统组合上不会轻易消除。

    一般来说,出于易于编码、可靠性和可移植性的原因,我建议您使用操作系统提供的功能。如果它提供高精度计时器,请使用适当的操作系统助手。

    (以防万一您尝试对加密系统进行时间攻击,那么您将不得不忍受 1. 这种随机性和 2. 出于充分的理由使系统不可预测的一般防御,所以函数对于时间可能不是确定性的。)

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

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

    【讨论】:

    • 谢谢。如何将进程绑定到一个处理器?您有使用 HPET 的示例吗?
    • @Łukasz Lew:在最后一段中阐明了最佳方法和 HPET。
    【解决方案2】:

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

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

    【讨论】:

    • 中位数似乎很健壮。但我不能忽视基准测试永远持续下去的情况。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      实际上在较新的 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 秒的时间

      【讨论】:

      • 能否提供链接或来源?
      【解决方案5】:

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

      一般来说,要缓解所有这些几乎是不可能的,但是您可以做一些事情来帮助每一个:

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

      但是,当然,到目前为止,进行此类计时的最佳方法是对非常大的数据进行多次计时,以最大限度地减少由您无法控制的事物引入的可变性。 (它永远无法真正抹去。)

      【讨论】:

      • 如果您要测量时钟周期,为什么要从基准测试中消除缓存未命中和分支错误预测?这些也会“在野外”发生,因此查看整个分布很重要,而不仅仅是最佳情况。
      【解决方案6】:

      大多数现代处理器都支持一组非凡的低级硬件性能计数器。 如果您真的想知道答案,包括对缓存未命中和上下文切换开销的实际测量,请获取PAPI (Performance API) toolkit,然后,在某些(尽管不是全部)操作系统上安装一个内核补丁,并且通过一些额外的努力,您重新关闭并运行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-16
        • 2013-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多