__volatile__
修饰符作用于
__asm__
块,可以强制编译器按照原样执行代码。如果不使用它,优化器可能会认为该代码可以被彻底删除或从循环中提取并缓存。
对于像rdtsc
指令这样的情况,这非常有用:
__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )
这不需要任何依赖,所以编译器可能会假设该值可以被缓存。使用Volatile可以强制使其读取新的时间戳。
当像这样单独使用时:
__asm__ __volatile__ ("")
实际上它并不会执行任何操作。但你可以将其扩展,以获得一个编译时内存屏障,这将禁止重排序任何内存访问指令:
__asm__ __volatile__ ("":::"memory")
rdtsc
指令是易变的好例子。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都有权重新排列所有这些语句。结果可能是执行asm块之后r1和r2已经被计算出来。为了解决这个问题,您需要添加一些强制序列化的屏障:
__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块中的"memory"说明符,它强制执行编译时屏障。在现代CPU上,您可以将
mfence:rdtsc
替换为更高效的
rdtscp
。