基于CPU周期计算的C / C ++ Linux x86_64中的分析

我正在使用以下代码来分析我的操作,以优化我的函数中的cpu周期。

static __inline__ unsigned long GetCC(void) { unsigned a, d; asm volatile("rdtsc" : "=a" (a), "=d" (d)); return ((unsigned long)a) | (((unsigned long)d) << 32); } 

我不认为这是最好的,因为即使连续两次通话也给我带来“33”的差异。 有什么建议 ?

我个人认为rdtsc指令很棒,可用于各种任务。 我不认为使用cpuid是准备rdtsc所必需的。 以下是我对rdtsc的理由:

  1. 由于我使用Watcom编译器,我使用“#pragma aux”实现了rdtsc,这意味着C编译器将生成内联指令,期望edx:eax中的结果,并通知其优化器eax和edx的内容已经改性。 与传统的_asm实现相比,这是一个巨大的改进,优化器将远离_asm附近的优化。 我还使用“#pragma aux”实现了divide_U8_by_U4,这样当我将clock_cycles转换为us或ms时,我就不需要调用lib函数。
  2. rdtsc的每次执行都会产生一些开销(如果它在作者的例子中被封装,则会更多),必须更多地考虑,测量的序列越短。 通常我没有比内部时钟频率的1/30更短的时间序列,这通常可以达到1/10 ^ 8秒(3 GHZ内部时钟)。 我使用这种测量作为指示,而不是事实。 知道这一点,我可以省去cpuid。 我衡量的次数越多,我就越接近事实。
  3. 为了可靠地测量,我将使用1/100 – 1/300范围i / e 0.03 – 0.1 us。 在此范围内,使用cpuid的额外准确性实际上是微不足道的。 我将此范围用于短序列计时。 这是我的“非标准”单元,因为它取决于CPU的内部时钟频率。 例如,在1 GHz机器上,我不会使用0.03 us,因为这会使我超出1/100限制,我的读数将成为指示。 在这里,我将使用0.1 us作为最短时间测量单位。 不会使用1/300,因为它太接近1 us(见下文)以产生任何显着差异。
  4. 对于更长的处理序列,我将两个rdtsc读数之间的差值除以3000(对于3 GHz)并将经过的时钟周期转换为我们。 实际上我使用(diff + 1500)/ 3000,其中1500是3000的一半。对于I / O等待,我使用毫秒=>(diff + 1500000)/ 3000000。 这些是我的“标准”单位。 我很少使用秒。
  5. 有时我会得到意想不到的缓慢结果然后我必须问自己:这是由于中断还是代码? 我测量了几次,看看它是否确实是一个中断。 在那种情况下……好的中断在现实世界中一直发生。 如果我的序列很短,那么下一次测量很可能不会被中断。 如果序列较长,则会更频繁地发生中断,并且我无能为力。
  6. 非常准确地测量长时间(我们或更低的时间和更长的ET)将增加在divide_U8_by_U4中获得除法exception的风险,因此我想到何时使用我们以及何时使用ms。
  7. 我也有基本统计的代码。 使用这个我记录最小值和最大值,我可以计算平均值和标准偏差。 这段代码非常重要,因此必须从测量的ET中减去自己的ET。
  8. 如果编译器正在进行广泛的优化并且您的读数存储在局部变量中,则编译器可以确定(“正确地”)可以省略代码。 避免这种情况的一种方法是将结果存储在公共(非静态,非堆栈)变量中。
  9. 应该在真实条件下测量在真实条件下运行的程序,没有办法解决这个问题。

至于时间戳计数器准确的问题我会说假设不同核心上的tsc是同步的(这是常态),在低活动期间存在CPU节流的问题以减少能量消耗。 测试时始终可以禁用function。 如果您在同一处理器上执行1 GHz或10 Mhz的指令,则经过的周期计数将是相同的,即使前者在1%的时间内完成后续计算。

试图计算单个函数执行的周期并不是正确的方法。 您的进程可以随时中断,以及由缓存未命中和分支错误预测引起的延迟这一事实意味着从呼叫到呼叫的周期数可能存在相当大的偏差。

正确的方法是:

  • 计算对函数进行大量调用所需的周期数或CPU时间(使用clock() ),然后对它们取平均值; 要么
  • 使用像Callgrind / kcachegrind这样的循环级仿真分析器。

顺便说一句,您需要在RDTSC之前执行串行化指令。 通常使用CPUID

你是在正确的轨道1 ,但你需要做两件事:

  1. rdtsc之前运行cpuid指令以刷新CPU管道(使测量更可靠)。 据我所知,它从托克斯注册到ex到edx
  2. 实时测量。 执行时间不仅仅是CPU周期(锁定争用,上下文切换和其他您无法控制的开销)。 用实时校准TSC滴答。 你可以在一个简单的循环中完成它,它可以测量gettimeofday (Linux,因为你没有提到平台)调用和rdtsc输出的测量值差异。 然后你可以告诉每个TSC滴答需要多少时间。 另一个考虑因素是跨CPU的TSC同步,因为每个核心可能有自己的计数器。 在Linux中你可以在/proc/cpuinfo看到它,你的CPU应该有一个constant_tsc标志。 我见过的大多数较新的Intel CPU都有这个标志。

1个人发现rdtscgettimeofday()等系统调用更精确,可用于细粒度测量。

您可能需要担心的另一件事是,如果您在多核计算机上运行,​​程序可能会被移动到另一个核心,该核心将具有不同的rdtsc计数器。 但是,您可以通过系统调用将进程固定到一个核心。

如果我试图测量这样的东西,我可能会将时间戳记录到数组中,然后在完成基准测试的代码之后再回来检查这个数组。 当您检查记录到时间戳数组的数据时,您应该记住,此数组将依赖于CPU缓存(如果您的数组很大,可能会分页),但您可以预取或在分析时记住这一点数据。 您应该在时间戳之间看到一个非常规则的时间增量,但是有几个尖峰并且可能有几个下降(可能来自移动到不同的核心)。 常规时间增量可能是您的最佳测量值,因为它表明没有外部事件影响这些测量。

话虽如此,如果您进行基准测试的代码具有不规则的内存访问模式或运行时间或依赖于系统调用(尤其是与IO相关的代码),那么您将很难将噪声与您感兴趣的数据分开。

TSC不是一个很好的时间衡量标准。 CPU对TSC的唯一保证是它单调上升(也就是说,如果你RDTSC一次,然后再做一次,第二次将返回高于第一次的结果)并且它将使它成为一个很长一段时间来环绕。

我是否理解为什么你这样做的原因是用它来包含其他代码以便你可以测量其他代码需要多长时间?

我相信你知道另一个好方法就是循环其他代码10 ^ 6次,秒表它,并称之为微秒。

一旦你测量了其他代码,我是否正确地假设您想知道哪些行值得优化,以减少所需的时间?

如果是这样的话,那你就是很好的。 您可以使用Zoom或LTProf等工具。 这是我最喜欢的方法。