Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/64.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
RDTSCP与RDTSC+;CPUID_C_Assembly_Linux Kernel_X86 - Fatal编程技术网

RDTSCP与RDTSC+;CPUID

RDTSCP与RDTSC+;CPUID,c,assembly,linux-kernel,x86,C,Assembly,Linux Kernel,X86,我正在做一些Linux内核计时,特别是在中断处理路径中。我一直在使用RDTSC进行计时,但最近我发现它不一定准确,因为指令可能会发生混乱 然后我试着: RDTSC+CPUID(此处按相反顺序)刷新管道,在虚拟机(我的工作环境)上由于超级调用等原因产生的开销高达60倍(!)。这包括启用和不启用硬件虚拟化 最近我遇到了RDTSCP*指令,它似乎做了RDTSC+CPUID所做的事情,但效率更高,因为它是一条较新的指令,相对而言,开销只有1.5x-2x 我的问题是:RDTSCP作为一个测量点是否真正准确

我正在做一些Linux内核计时,特别是在中断处理路径中。我一直在使用RDTSC进行计时,但最近我发现它不一定准确,因为指令可能会发生混乱

然后我试着:

  • RDTSC+CPUID(此处按相反顺序)刷新管道,在虚拟机(我的工作环境)上由于超级调用等原因产生的开销高达60倍(!)。这包括启用和不启用硬件虚拟化

  • 最近我遇到了RDTSCP*指令,它似乎做了RDTSC+CPUID所做的事情,但效率更高,因为它是一条较新的指令,相对而言,开销只有1.5x-2x

  • 我的问题是:RDTSCP作为一个测量点是否真正准确,它是否是进行计时的“正确”方法

    更清楚地说,我的时间安排基本上是这样的,在内部:

    • 保存当前循环计数器值
    • 执行一种基准测试(即:磁盘、网络)
    • 将当前和上一个周期计数器的增量添加到累加器值中,并在每个中断中增加一个计数器
    • 最后,将增量/累加器除以中断数,得到每个中断的平均周期成本

    *第27页

    有关您从cpuid指令中看到的开销的完整讨论,请访问。使用rdtsc时,需要使用cpuid以确保执行管道中没有其他指令。rdtscp指令从本质上刷新管道。(参考的SO线程也讨论了这些要点,但我在这里讨论了它们,因为它们也是您问题的一部分)

    如果处理器不支持rdtscp,则仅“需要”使用cpuid+rdtsc。否则,rdtscp就是您想要的,并且会准确地为您提供所需的信息

    这两条指令都提供了一个64位、单调递增的计数器,表示处理器上的周期数。如果这是您的模式:

    uint64_t s, e;
    s = rdtscp();
    do_interrupt();
    e = rdtscp();
    
    atomic_add(e - s, &acc);
    atomic_add(1, &counter);
    
    根据您的读取发生的位置,您的平均测量值可能仍有1的偏差。例如:

       T1                              T2
    t0 atomic_add(e - s, &acc);
    t1                                 a = atomic_read(&acc);
    t2                                 c = atomic_read(&counter);
    t3 atomic_add(1, &counter);
    t4                                 avg = a / c;
    
    现在还不清楚“[a]t the end”是否指的是一个可以以这种方式竞赛的时代。如果是这样的话,你可能想计算一个运行平均线或移动平均线与你的增量

    侧重点:

  • 如果确实使用cpuid+rdtsc,则需要减去cpuid指令的开销,这可能很难确定您是否在VM中(取决于VM如何实现此指令)。这就是为什么您应该坚持使用rdtscp
  • 在循环内执行rdtscp通常是个坏主意。我经常看到一些微基准点,它们可以做
  • --

    与之前的基准测试相比,在现实生活中你会看到更高效、更准确的结果

    RDTSCP作为一个测量点是否真正准确,它是计时的“正确”方式吗

    现代x86 CPU可以动态调整频率,通过欠时钟(例如Intel的SpeedStep)节省电源,并通过过时钟(例如Intel的Turbo boost)提高重载性能。然而,这些现代处理器上的时间戳计数器以恒定速率计数(例如,在Linux的/proc/cpuinfo中查找“constant_tsc”标志)

    所以你问题的答案取决于你真正想知道什么。除非禁用动态频率缩放功能(例如在BIOS中),否则时间戳计数器不能再用于确定经过的周期数。但是,仍然可以依赖时间戳计数器来确定经过的时间(需要谨慎,但我在C中使用了
    clock\u gettime
    ,请参见我的答案的末尾)

    为了对我的矩阵乘法代码进行基准测试并将其与理论上的最佳值进行比较,我需要知道经过的时间和经过的周期(或者更确切地说是测试期间的有效频率)

    让我介绍三种不同的方法来确定经过的循环数

  • 在BIOS中禁用动态频率缩放并使用时间戳计数器
  • 对于英特尔处理器,从性能监视器计数器请求
    核心时钟周期
    
  • 第一种方法是最可靠的,但它需要访问BIOS并影响您运行的所有其他方法的性能(当我在我的i5-4250U上禁用动态频率缩放时,它以恒定的1.3 GHz运行,而不是以2.6 GHz为基数)。仅为基准测试而更改BIOS也很不方便

    第二种方法在您不想禁用动态频率标度和/或不想为您没有物理访问权限的系统禁用动态频率标度时非常有用。但是,性能监视器计数器需要只有内核或设备驱动程序才能访问的特权指令

    第三种方法在没有物理访问权限和特权访问权限的系统上很有用。这是我在实践中使用最多的方法。原则上它是最不可靠的,但实际上它和第二种方法一样可靠

    下面是我如何用C确定经过的时间(以秒为单位)

    #定义计时器(类型)时钟(实时)
    timespec time1、time2;
    时钟获取时间(定时器类型和定时器1);
    foo();
    时钟获取时间(定时器类型和定时器2);
    double dtime=时间差(时间1,时间2);
    双时差(timespec开始、timespec结束)
    {
    时间样本温度;
    
    如果((end.tv_nsec-start.tv_nsec)以下代码将确保
    rdstcp
    在正确的时间启动。
    RDTSCP
    不能执行得太早,但它可以执行得太晚,因为CPU可以将指令移到
    RDTSCP
    之后执行

    为了防止这种情况发生,我们根据
    rdstcp
    将其输出放在edx:eax中这一事实创建了一个虚假的依赖链

    rdt
    
    for (int i = 0; i < SOME_LARGEISH_NUMBER; i++) {
       s = rdtscp();
       loop_body();
       e = rdtscp();
       acc += e - s;
    }
    
    printf("%"PRIu64"\n", (acc / SOME_LARGEISH_NUMBER / CLOCK_SPEED));
    
    s = rdtscp();
    for (int i = 0; i < SOME_LARGEISH_NUMBER; i++) {
       loop_body();
    }
    e = rdtscp();
    printf("%"PRIu64"\n", ((e-s) / SOME_LARGEISH_NUMBER / CLOCK_SPEED));
    
    #define TIMER_TYPE CLOCK_REALTIME
    
    timespec time1, time2;
    clock_gettime(TIMER_TYPE, &time1);
    foo();
    clock_gettime(TIMER_TYPE, &time2);
    double dtime = time_diff(time1,time2);
    
    double time_diff(timespec start, timespec end)
    {
        timespec temp;
        if ((end.tv_nsec-start.tv_nsec)<0) {
            temp.tv_sec = end.tv_sec-start.tv_sec-1;
            temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
        } else {
            temp.tv_sec = end.tv_sec-start.tv_sec;
            temp.tv_nsec = end.tv_nsec-start.tv_nsec;
        }
        return (double)temp.tv_sec +  (double)temp.tv_nsec*1E-9;
    }
    
    mfence
    lfence
    rdtsc
    shl     rdx, 0x20
    or      rax, rdx
    
    rdtscp
    lfence
    shl     rdx, 0x20
    or      rax, rdx