Clang 优化 RDTSC asm 块,认为重复的块产生与前一个块相同的结果。这合法吗?

san*_*orn 5 c++ x86 gcc inline-assembly rdtsc

假设我们有一些相同的汇编的重复,其中包含RDTSC诸如

    volatile size_t tick1;
    asm ( "rdtsc\n"           // Returns the time in EDX:EAX.
          "shl $32, %%rdx\n"  // Shift the upper bits left.
          "or %%rdx, %q0"     // 'Or' in the lower bits.
          : "=a" (tick1)
          : 
          : "rdx");
    
    this_thread::sleep_for(1s);

    volatile size_t tick2;    
    asm ( "rdtsc\n"          // clang's optimizer just thinks this asm yields
          "shl $32, %%rdx\n" // the same bits as above, so it just loads
          "or %%rdx, %q0"    // the result to qword ptr [rsp + 8]
          : "=a" (tick2)     // 
          :                  //   mov     qword ptr [rsp + 8], rbx
          : "rdx");

    printf("tick2 - tick1 diff : %zu cycles\n", tick2 - tick1);
    printf("CPU Clock Speed    : %.2f GHz\n\n", (double) (tick2 - tick1) / 1'000'000'000.);

Run Code Online (Sandbox Code Playgroud)
Clang++ 的优化器(即使使用 `-O1` )认为这两个 asm 块产生相同的结果:

tick2 - tick1 diff : 0 cycles
CPU Clock Speed    : 0.00 GHz

tick1              : bd806adf8b2
this_thread::sleep_for(1s)
tick2              : bd806adf8b2
Run Code Online (Sandbox Code Playgroud)
当关闭 Clang 的优化器时,第二个块会按预期产生进度标记:

tick2 - tick1 diff : 2900160778 cycles
CPU Clock Speed    : 2.90 GHz

tick1              : 14ab6ab3391c
this_thread::sleep_for(1s)
tick2              : 14ac17902a26
Run Code Online (Sandbox Code Playgroud)
第一个 GCC g++“似乎”不受此影响。
tick2 - tick1 diff : 2900226898 cycles
CPU Clock Speed    : 2.90 GHz

tick1              : 20e40010d8a8
this_thread::sleep_for(1s)
tick2              : 20e4aceecbfa
Run Code Online (Sandbox Code Playgroud)

[居住]

但是,让我们在后面添加tick3确切的内容asmtick2

    volatile size_t tick1;
    asm ( "rdtsc\n"           // Returns the time in EDX:EAX.
          "shl $32, %%rdx\n"  // Shift the upper bits left.
          "or %%rdx, %q0"     // 'Or' in the lower bits.
          : "=a" (tick1)
          : 
          : "rdx");
    
    this_thread::sleep_for(1s);

    volatile size_t tick2;    
    asm ( "rdtsc\n"          // clang's optimizer just thinks this asm yields
          "shl $32, %%rdx\n" // the same bits as above, so it just loads
          "or %%rdx, %q0"    // the result to qword ptr [rsp + 8]
          : "=a" (tick2)     // 
          :                  //   mov     qword ptr [rsp + 8], rbx
          : "rdx");

    volatile size_t tick3;
    asm ( "rdtsc\n"          
          "shl $32, %%rdx\n"   
          "or %%rdx, %q0"    
          : "=a" (tick3)
          : 
          : "rdx");
Run Code Online (Sandbox Code Playgroud)

事实证明,GCC 认为tick3必须asm产生与 相同的值,tick2因为“显然”没有外部副作用,因此它只是从 重新加载tick2。即使这是错误的,但它有一个很强的观点。

tick2 - tick1 diff : 2900209182 cycles
CPU Clock Speed    : 2.90 GHz

tick1              : 5670bd15088e
this_thread::sleep_for(1s)
tick2              : 567169f2b6ac
tick3              : 567169f2b6ac
Run Code Online (Sandbox Code Playgroud)

[居住]


在 C 模式下,GCC 和 Clang 的优化器都会受到影响。
换句话说,即使两者都优化了包含以下内容的块-O1的重复:asmrdtsc

tick2 - tick1 diff : 0 cycles
CPU Clock Speed    : 0.00 GHz

tick1              : 324ab8f5dd2a
thrd_sleep(&(struct timespec){.tv_sec=1}, nullptr)
tick2              : 324ab8f5dd2a
tick3_rdx          : 324b65d3368c
Run Code Online (Sandbox Code Playgroud)

[居住]

事实证明,所有优化器都可以对相同的非语句执行公共子表达式消除volatile asm,因此 asm 语句RDTSC需要为volatile

Dav*_*son 9

C++ 标准未涵盖内联汇编,因此我不确定您在这里对“合法”的定义是什么。不过,您所看到的行为对我来说是有意义的,因为您正在运行内联程序集的副作用(即您的程序集没有实现纯函数)并且您忘记使用关键字volatile。来自GCC 内联汇编文档

扩展 asm 语句的典型用途是操作输入值以产生输出值。然而,您的 asm 语句也可能会产生副作用。如果是这样,您可能需要使用 volatile 限定符来禁用某些优化。

还:

如果 GCC 的优化器确定不需要输出变量,有时会丢弃 asm 语句。此外,如果优化器认为代码将始终返回相同的结果(即,其输入值在调用之间不会发生变化),则优化器可能会将代码移出循环。使用 volatile 限定符会禁用这些优化。

如果您在问题消失volatile后立即插入关键字。asm

PS 不要使用内联汇编,只需包含x86intrin.h然后使用__rdtsc()函数即可。