首先,lfence
可能与 一样强序列化cpuid
,也可能不是。如果您关心性能,请检查并查看是否可以找到lfence
足够强大的证据(至少对于您的用例而言)。如果两者都不足以在 AMD 和 Intel 上进行序列化,则可能甚至使用两者都mfence; lfence
可能比 更好。(我不确定,请参阅我的链接评论)。cpuid
mfence
lfence
2. 是的,所有不告诉编译器 asm 语句写入 E[AD]X 的都是危险的,并且可能会导致难以调试的怪异。(即您需要使用(虚拟)输出操作数或破坏者)。
您需要volatile
,因为您希望执行 asm 代码以产生序列化的副作用,而不是产生输出。
如果您不想将 CPUID 结果用于任何事情(例如,通过序列化和查询某些内容来执行双重任务),您应该简单地将寄存器列为 clobber,而不是输出,因此您不需要任何 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__
我想知道所有这些电话之间有什么区别
首先,这些都不是“电话”。它们是 asm语句,并内联到您使用它们的函数中。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本身。 带有内存破坏器的 ( ) asm 语句是一个编译器屏障,可防止在其上重新排序加载或存储。volatile
优化器根本不关心第一个字符串文字中的内容,只关心约束/clobbers,因此asm volatile("anything" ::: register clobbers, "memory")
也是仅编译时的内存屏障。我假设这是您想要的,用于序列化一些内存操作。
"0"(level)
是第一个操作数 ( "=a"
) 的匹配约束。您同样可以编写"a"(level)
,因为在这种情况下,编译器无法选择要选择的寄存器;输出约束只能由 满足eax
。您也可以用作"+a"(eax)
输出操作数,但是您必须eax=level
在 asm 语句之前设置。x87 堆栈的东西有时需要匹配约束而不是读写操作数;我认为这曾在一个 SO 问题中出现过。但除了像这样奇怪的东西之外,优点是能够使用不同的 C 变量进行输入和输出,或者根本不使用变量作为输入。(例如文字常量或左值(表达式))。
无论如何,告诉编译器提供输入可能会导致额外的指令,例如会level=0
导致. 如果之前不需要任何归零的寄存器,这将是对指令的浪费。通常对输入进行异或归零会破坏对先前值的依赖,但这里 CPUID 的全部意义在于它正在序列化,因此它必须等待所有先前的指令完成执行。确保早点准备是没有意义的;如果您不关心输出,甚至不要告诉编译器您的 asm 语句需要输入xor
eax
eax
. 编译器使得在没有开销的情况下使用未定义/未初始化的值变得困难或不可能;有时未初始化 C 变量会导致从堆栈中加载垃圾或将寄存器清零,而不是仅使用寄存器而不先写入寄存器。