我们认为我们在GCC
架构上使用GCC
(或X86_64
- 兼容)编译器,eax
,ebx
,{{ 1}},ecx
和edx
是指令的输入和输出变量(level
或unsigned int
)(如here)。
unsigned int*
asm("CPUID":::);
asm volatile("CPUID":::);
asm volatile("CPUID":::"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx)::"memory");
asm volatile("CPUID":"=a"(eax):"0"(level):"memory");
asm volatile("CPUID"::"a"(level):"memory"); // Not sure of this syntax
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
asm("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
asm volatile("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level));
用作serializing instruction的上下文中(例如对指令的输出不会做任何事情)。 答案 0 :(得分:2)
首先,lfence
可能与cpuid
一样强烈序列化,也可能不像。如果您关心性能,请检查并查看是否可以找到lfence
足够强大的证据(至少对您的用例而言)。如果cpuid
和mfence
都不足以在AMD和英特尔上进行序列化,那么偶数using both mfence; lfence
可能会优于lfence
。 (我不确定,请看我的链接评论)。
2.是的,所有那些不告诉编译器asm语句写E [A-D] X是危险的并且可能导致难以调试的怪异。 (即你需要使用(虚拟)输出操作数或clobbers)。
您需要volatile
,因为您希望执行asm代码以实现序列化的副作用,而不是生成输出。
如果您不想将CPUID结果用于任何事情(例如,通过序列化和查询某些内容来执行双重任务),您应该简单地将寄存器列为clobbers而不是输出,所以你不需要任何C变量来保存结果。
// volatile is already implied because there are no output operands
// but it doesn't hurt to be explicit.
// Serialize and block compile-time reordering of loads/stores across this
asm volatile("CPUID"::: "eax","ebx","ecx","edx", "memory");
// the "eax" clobber covers RAX in x86-64 code, you don't need an #ifdef __i386__
我想知道所有这些电话之间有什么区别
首先,这些都不是"电话"。他们是语句,并且内联到您使用它们的函数中。 CPUID本身不是"呼叫"或者,虽然我猜你可以把它看作是调用内置于CPU的微代码功能。但按照这种逻辑,每条指令都是一个"呼叫" mul rcx
接收RAX和RCX中的输入,并返回RDX:RAX。
前三个(以及后一个没有输出,只有一个level
输入)通过RDX破坏RAX而不告诉编译器。它将假设那些寄存器仍然保留它们保留的内容。他们显然无法使用。
asm("CPUID":"=a"(eax),"=b"(ebx),"=c"(ecx),"=d"(edx):"0"(level):"memory");
(没有volatile
的人将会优化掉)。如果你使用它们,它仍然可以从循环中提升。非volatile
asm语句被优化器视为纯函数,没有副作用。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#index-asm-volatile
它有一个记忆破坏,但(我认为)并没有阻止它优化,它只是意味着如果/何时/它 运行的地方,它可以做任何变量可能的读/写被同步到内存,因此内存内容与C抽象机器在该点上的内容相匹配。但是,这可能会排除那些没有获得地址的当地人。
asm("" ::: "memory")
与std::atomic_thread_fence(std::memory_order_seq_cst)
非常相似,但请注意asm
语句没有输出,因此隐含volatile
。 那是为什么它没有被优化掉,而不是因为"memory"
clobber本身。 带有内存clobber的(volatile
)asm语句是一个编译器屏障,可以阻止重新排序加载或存储。
优化器并不关心第一个字符串文字中的所有内容,只关注约束/符号,因此asm volatile("anything" ::: register clobbers, "memory")
也只是一个编译时内存屏障。我认为这是你想要的,序列化一些内存操作。
"0"(level)
是第一个操作数("=a"
)的匹配约束。您可以同样编写"a"(level)
,因为在这种情况下,编译器无法选择要选择的寄存器;输出约束只能由eax
满足。您也可以使用"+a"(eax)
作为输出操作数,但是您必须在asm语句之前设置eax=level
。 x87堆栈内容有时需要匹配约束而不是读写操作数;我认为在SO问题中出现过一次。但除了像这样奇怪的东西之外,其优点是可以使用不同的C变量进行输入和输出,或者根本不使用变量来输入。 (例如,文字常量或左值(表达式))。
无论如何,告诉编译器提供输入可能会导致额外的指令,例如: level=0
会导致xor
- 归零eax
。如果它之前没有需要归零寄存器,那将浪费指令。通常,对输入进行xor-zeroing会破坏对先前值的依赖性,但这里CPUID的整个点是它的序列化,因此它必须等待所有先前的指令完成执行无论如何。确保eax
提早就绪是没有意义的; 如果您不关心输出,请不要告诉编译器您的asm语句需要输入。编译器很难或不可能使用未定义/未初始化的值而没有开销;有时将C变量保留为未初始化将导致从堆栈加载垃圾,或者将寄存器归零,而不是仅使用寄存器而不先写入它。