测量工具的时钟周期不一致,这种不一致的周期究竟来自哪里?

问题描述

我花了很多时间来测量给定指令的精确时钟周期,部分代码是用 C 编写的。但是,我永远无法准确测量运行时需要多少个周期,我使用了 PAPI,“rtdsc”硬件计数器,时钟(),带有“CLOCK_REALTIME”的clock_gettime。然而,对于每次运行,我得到不同的数字,(我不想将程序迭代超过 100000 次并获得平均值,在我的情况下不起作用!即使有效,平均值仍然是在我看来不是确切的方式),

我很惊讶没有精确的测量工具,因为我们确切地知道有多少指令,我们怎么可能没有合适的测量工具!

我用 oprofile 和 vtune(里面使用了 pref)进行了分析,我确切地知道发生了什么,但我如何才能准确地测量它?恐怕没有办法!

对于下面的示例代码调用斐波那契函数,没办法得到准确的周期数!

#include <stdio.h>
#include <time.h>
#include <math.h>
#include <stdint.h>

#ifdef PAPI
#include <papi.h>
#endif

uint64_t fib(int n) { return n < 2 ? (uint64_t)n : fib(n-1) + fib(n-2); }

#define rdtscll(val) {                                           \
    unsigned int __a,__d;                                        \
    asm volatile("rdtsc" : "=a" (__a),"=d" (__d));              \
    (val) = ((unsigned long)__a) | (((unsigned long)__d)<<32);   \
}

int main(int argc,char **argv)
{
    struct timespec tv;
    long long start,stop;
#ifdef PAPI
    //  gcc test.c -I/${PAPI_DIR}/include -L/${PAPI_DIR}/lib -O3 -o test -lpapi
    if (PAPI_library_init(PAPI_VER_CURRENT) != PAPI_VER_CURRENT)
        exit(1);
    start = PAPI_get_real_cyc();
    #ifdef math
    fib(10);
    #endif  
    stop = PAPI_get_real_cyc();
    printf("total cycles : %lld\n",stop-start);
#endif  
#ifdef clk
    start = clock();
    #ifdef math
    fib(10);
    #endif  
    stop = clock();
    printf("total cycles : %lld\n",stop-start);
#endif
#ifdef Wtime
    clock_gettime(CLOCK_REALTIME,&tv);
    start= (tv.tv_sec) * 1000000000 + (tv.tv_nsec);
    #ifdef math
    fib(10);
    #endif  
    clock_gettime(CLOCK_REALTIME,&tv);
    stop= (tv.tv_sec) * 1000000000 + (tv.tv_nsec);
    printf("total time : %lld\n",stop-start);
#endif
#ifdef hardware_counter
    rdtscll(start);
    #ifdef math
    fib(10);
    #endif  
    rdtscll(stop);  
    printf("total cycles : %lld\n",stop-start);
#endif
    return 0;
}

例如,我将其中一些的 objdump 输出在这里 rtdsc:

0000000000400450 <main>:
  400450:   48 83 ec 08             sub    $0x8,%rsp
  400454:   0f 31                   rdtsc  
  400456:   89 c0                   mov    %eax,%eax
  400458:   48 c1 e2 20             shl    $0x20,%rdx
  40045c:   48 09 c2                or     %rax,%rdx
  40045f:   b8 09 00 00 00          mov    $0x9,%eax
  400464:   49 89 d3                mov    %rdx,%r11
  400467:   83 f8 01                cmp    $0x1,%eax
  40046a:   7e 5c                   jle    4004c8 <main+0x78>
  40046c:   44 8d 48 fe             lea    -0x2(%rax),%r9d
  400470:   44 8d 50 fd             lea    -0x3(%rax),%r10d
  400474:   44 8d 40 ff             lea    -0x1(%rax),%r8d
  400478:   44 89 c8                mov    %r9d,%eax
  40047b:   83 e0 fe                and    $0xfffffffe,%eax
  40047e:   41 29 c2                sub    %eax,%r10d
  400481:   44 89 c7                mov    %r8d,%edi
  400484:   e8 37 01 00 00          callq  4005c0 <xfib>
  400489:   41 83 e8 02             sub    $0x2,%r8d
  40048d:   45 39 d0                cmp    %r10d,%r8d
  400490:   75 ef                   jne    400481 <main+0x31>
  400492:   41 83 f9 ff             cmp    $0xffffffff,%r9d
  400496:   44 89 c8                mov    %r9d,%eax
  400499:   75 cc                   jne    400467 <main+0x17>
  40049b:   0f 31                   rdtsc  
  40049d:   89 c0                   mov    %eax,%eax
  40049f:   48 c1 e2 20             shl    $0x20,%rdx
  4004a3:   be 24 0a 40 00          mov    $0x400a24,%esi
  4004a8:   48 09 c2                or     %rax,%rdx
  4004ab:   bf 01 00 00 00          mov    $0x1,%edi
  4004b0:   31 c0                   xor    %eax,%eax
  4004b2:   4c 29 da                sub    %r11,%rdx
  4004b5:   e8 76 ff ff ff          callq  400430 <__printf_chk@plt>
  4004ba:   31 c0                   xor    %eax,%eax
  4004bc:   48 83 c4 08             add    $0x8,%rsp
  4004c0:   c3                      retq   
  4004c1:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
  4004c8:   44 8d 48 fe             lea    -0x2(%rax),%r9d
  4004cc:   41 83 f9 ff             cmp    $0xffffffff,%r9d
  4004d0:   44 89 c8                mov    %r9d,%eax
  4004d3:   75 92                   jne    400467 <main+0x17>
  4004d5:   eb c4                   jmp    40049b <main+0x4b>
  4004d7:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  4004de:   00 00 

在不调用一个库、标准输出等的情况下,有什么方法可以准确和一致地测量计算机在一部分代码之间使用的时钟周期数?

感谢您的帮助,

编辑: 感谢大家的评论,关于框架,我在运行 ubuntu 16.04 和 ubuntu 18.04 的物理机上使用 x86_64 和 aarch64。在 aarch64 上的 PAPI 情况下无法通过验证测试,所以我跳过了 PAPI。但是,我也使用“perf”尝试了这个主题,但结果相同, 性能循环计数器 -> https://stackoverflow.com/a/64898073/3101659

我认为这与计算机体系结构的基础有关,在冯诺依曼机器中,使用“随机内存访问”、“brach 预测”和其他东西是我们无法在我们的程序中预测和实现相同周期的原因. 如果我在 FPGA 设备上使用 HLS 运行此程序,我可以准确地测量设备逐个周期使用的周期数,而在基于冯诺依曼的机器等传统机器上,这是不可能的。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)