如何在没有函数调用的情况下在Linux中检索处理器时间?

时间:2017-10-29 18:08:13

标签: c++ c linux unix assembly

我需要计算一部分(C ++)代码的运行时间,并希望通过查找执行代码期间经过的时钟周期数来实现这一目的。

我想找到代码开头的处理器时间和最后的处理器时间,然后减去它们以找到经过的刻度数。

可以使用clock function完成此操作。然而,我测量的时间需要非常精确并且使用函数调用被证明是非常具有侵入性的,因为调用者保存的寄存器分配器在每次调用时都会溢出许多变量。

因此,我无法使用任何函数调用,需要自己检索处理器时间。汇编代码没问题。

我使用的是Debian和i7 Intel处理器。我无法使用分析器,因为它过于干扰。

4 个答案:

答案 0 :(得分:4)

您应该阅读time(7)。请注意,即使用汇编语言编写,您的程序也会在任意时刻重新安排(可能每毫秒context switch;同时查看/proc/interrupts并查看proc(5))。那么任何硬件定时器都没有意义。即使using RDTSC x86-64机器指令读取hardware timestamp counter也没用(因为在任何上下文切换之后它都是错误的,并且Linux内核正在执行preemptive调度,这确实发生在任何时间)。

您应该考虑clock_gettime(2)。由于vdso(7),它非常快(我的i5-4690S大约需要3.5或4纳秒,当测量数千次调用时)。顺便说一句,它是system call,因此您可以直接编写执行它们的汇编程序指令。我不认为这是值得的(并且可能比vdso调用慢)。

BTW,任何形式的分析或基准测试都会产生某种干扰。

最后,如果您的基准测试功能运行得非常快(远小于一微秒),cache未命中变得非常重要甚至占主导地位(请记住,需要有效访问DRAM模块的L3缓存未命中数百纳秒,足以在L1 I-cache中运行数百个机器指令。您可能(也可能应该)尝试对几个(数百个)连续调用进行基准测试。但是你无法精确准确地测量。

因此,我认为不能比使用clock_gettime 做得更好我不明白为什么它不够好对于您的情况... BTW,clock(3)正在使用clock_gettime调用CLOCK_PROCESS_CPUTIME_ID,因此恕我直言,它应该足够,而且更简单。

换句话说,我认为避免任何函数调用是你的错误观念。请记住,函数调用开销比缓存未命中要便宜很多!

请参阅this answer相关问题(与您的不清楚);考虑使用perf(1)gprof(1)oprofile(1)time(1)。请参阅this

最后,您应该考虑从编译器中询问更多optimizations。您是否考虑过使用g++ -O3 -flto -march=native编译和链接(使用链接时优化)。

如果您的代码具有数字和矢量性质(显然且可以大规模并行化),您甚至可以考虑花费的开发时间来移植其核心代码(数字compute kernels )在GPGPUOpenCL CUDA上。但你确定值得这么努力吗?在更换硬件时,您需要调整和重新开发代码!

您还可以重新设计您的应用,以使用multi-threadingJIT compilationpartial evaluation以及metaprogramming技术,multiprocessingcloud-computing(带{ {3}},例如inter-process communication - s,可能使用socket(7)或其他消息传递库。这可能需要的开发。有0mq

(不要忘记考虑开发成本;尽可能选择算法改进。)

答案 1 :(得分:2)

  

分析工具是否允许我测量特定循环的运行时间?

将循环置于可执行文件中并运行perf stat ./a.out对其进行计时并计算缓存未命中,分支未命中以及其他任何内容。如果太短,请在其周围缠绕重复循环。然后你有一个microbenchmark,你可以用硬件性能计数器进行分析,分析它如何解码为uops,它们是否按你想象的那样安排,等等。

检查asm输出以确保它不会优化任何内容,或者在外部重复循环的迭代中进行优化。在C中,您可以使用asm volatile("" ::: "memory");作为编译器内存屏障,或者您可以使用asm("" : "+g"(my_variable))强制编译器在每个步骤中在寄存器中具有C变量,并使用空的asm语句作为依赖链。

请参阅我在Can x86's MOV really be "free"? Why can't I reproduce this at all?上的答案,了解使用纯asm制作静态二进制文件的示例。 (启动开销可忽略不计,因此整个可执行文件的性能计数器噪音较小。但如果运行1000毫秒并且启动只需几微秒,则测量中的精度和可重复性非常高。)

它仍然是一个微基准测试,通过高速缓存和分支预测来测量您的循环,而不是像您的循环在整个程序的上下文中偶尔运行时发生的情况。不要让这个愚弄你把它展开太多;在微基准测试中获胜但在实践中可能会受到伤害。

  

另外,我认为分析器会使用很多函数调用,这会再次在我的数据中产生很多噪音。

使用硬件性能计数器的分析器非常非侵入式。

使用perf record ./main-program / perf report -Mintel会在代码中显示热门asm指令的统计采样地图。 (但请记住,循环通常会在等待输入的指令中收费,而不是生成缓慢的指令。)

答案 2 :(得分:2)

在x86上,您可以使用rdpmc指令执行此操作,该指令允许您从用户空间 1 读取性能监视器计数器。您不需要任何函数调用来使用它:您可以将rdpmc指令与要测量的代码一起插入。

特别是,您可以使用的其中一个计数器是CPU_CLK_UNHALTED.CORE,它可以让您测量较小的代码段以降低周期精度。

如果您可以将要测量的程序部分隔离到一个小的基准测试中,您可以使用我正在编写的Linux兼容的基准程序uarch-bench来计算代码的时间,它会照顾到设置计数器的大部分复杂性,重复运行基准并排除异常值。

上述基准测试使用libpfc从用户空间读取英特尔PMU。它专门用于解决此类情况。如果您想直接在现有流程中测量代码的某些部分的周期,可以使用libpfc本身,使用您要测量的代码周围的PFSTARTPFCEND宏。这些是直接内联测量代码的宏:它不涉及任何函数调用。

1 至少只要设置CR4.PCE,这在Linux上相对简单。

答案 3 :(得分:1)

你有很多理由说你想做的事情不会给你带来好成绩,但是没有人提到如何做你所要求的事情,所以你可以亲眼看到。

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

rdtsc未进行序列化,因此为了防止它在硬件中重新排序(并在之前的说明完成之前执行),您可能需要使用rdtscplfence; rdtsc

你的i7上的

lfence; rdtsc也会在以后的指令停止运行之前停止。请参阅Difference between rdtscp, rdtsc : memory and cpuid / rdtsc?