我研究了一些C代码
http://www.mcs.anl.gov/~kazutomo/rdtsc.html
他们使用诸如__inline__
,__asm__
等之类的东西,如下所示:
代码1:
static __inline__ tick gettick (void) {
unsigned a, d;
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
return (((tick)a) | (((tick)d) << 32));
}
代码2:volatile int __attribute__((noinline)) foo2 (int a0, int a1) {
__asm__ __volatile__ ("");
}
我想知道code1和code2是做什么的?(编者注:对于此特定的RDTSC用例,首选内部函数:How to get the CPU cycle count in x86_64 from C++?另请参见https://gcc.gnu.org/wiki/DontUseInlineAsm)
最佳答案
__volatile__
块上的__asm__
修饰符强制编译器的优化器按原样执行代码。如果没有它,优化器可能会认为它可以直接删除,也可以退出循环并进行缓存。
这对于rdtsc
指令很有用,如下所示:
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )
这没有依赖关系,因此编译器可能会假定可以缓存该值。 Volatile 用于强制其读取新的时间戳。
单独使用时,如下所示:
__asm__ __volatile__ ("")
它实际上不会执行任何操作。但是,您可以扩展它,以获取不允许重新排序任何内存访问指令的编译时内存障碍:
__asm__ __volatile__ ("":::"memory")
rdtsc
指令是volatile的一个很好的例子。 rdtsc
通常在需要时间执行某些指令的时间时使用。想象一下这样的代码,您想在其中计时r1
和r2
的执行时间:__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )
在这里,实际上允许编译器缓存时间戳,有效的输出可能显示每行执行的时钟恰好为0。显然,这不是您想要的,因此您引入
__volatile__
来防止缓存:__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))
现在,您每次都将获得一个新的时间戳,但是仍然存在一个问题,即允许编译器和CPU重新对所有这些语句进行排序。在已经计算出r1和r2之后,它可能最终执行asm块。要解决此问题,您将添加一些强制序列化的障碍:
__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")
请注意此处的
mfence
指令,该指令强制执行CPU端屏障,而volatile块中的“内存”说明符则强制执行编译时屏障。在现代CPU上,您可以将mfence:rdtsc
替换为rdtscp
,以提高效率。关于c - __asm__ __volatile__在C中做什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26456510/