C++ 使用带序列化指令的内联程序集 我们认为在 x86y64 体系结构中使用 GCC (或代码> GCC < /代码>兼容)编译器,并且该代码的输入>输出> EBX < /代码>, ECX , EDX < /代码>和级别>代码>是变量(未签名INT/或无符号INT* >)(像) 我不习惯内联汇编语法,我想知道在我只想使用CPUID作为一个函数的上下文中,所有这些调用之间的区别是什么(例如,不会对指令的输出执行任何操作) 这些电话中的一些会导致错误吗 这些调用中的哪一个最适合(考虑到我希望开销尽可能小,但同时要尽可能实现“最强”的序列化)

C++ 使用带序列化指令的内联程序集 我们认为在 x86y64 体系结构中使用 GCC (或代码> GCC < /代码>兼容)编译器,并且该代码的输入>输出> EBX < /代码>, ECX , EDX < /代码>和级别>代码>是变量(未签名INT/或无符号INT* >)(像) 我不习惯内联汇编语法,我想知道在我只想使用CPUID作为一个函数的上下文中,所有这些调用之间的区别是什么(例如,不会对指令的输出执行任何操作) 这些电话中的一些会导致错误吗 这些调用中的哪一个最适合(考虑到我希望开销尽可能小,但同时要尽可能实现“最强”的序列化),c++,gcc,x86,inline-assembly,cpuid,C++,Gcc,X86,Inline Assembly,Cpuid,首先,lfence可能与cpuid一样具有很强的序列化能力,也可能没有。如果您关心性能,请查看是否可以找到证据证明lfence足够强大(至少对于您的用例而言)。甚至可能比cpuid更好,如果mfence和lfence都不足以在AMD和Intel上进行序列化。(我不确定,请参阅我的链接评论) 2.是的,所有不告诉编译器asm语句写入E[A-D]X的语句都是危险的,并且可能会导致难以调试的异常(即,您需要使用(虚拟)输出操作数或clobber) 您需要volatile,因为您希望执行asm代码是为

首先,
lfence
可能与
cpuid
一样具有很强的序列化能力,也可能没有。如果您关心性能,请查看是否可以找到证据证明
lfence
足够强大(至少对于您的用例而言)。甚至可能比
cpuid
更好,如果
mfence
lfence
都不足以在AMD和Intel上进行序列化。(我不确定,请参阅我的链接评论)


2.是的,所有不告诉编译器asm语句写入E[A-D]X的语句都是危险的,并且可能会导致难以调试的异常(即,您需要使用(虚拟)输出操作数或clobber)

您需要
volatile
,因为您希望执行asm代码是为了产生序列化的副作用,而不是产生输出

如果您不想将CPUID结果用于任何事情(例如,通过序列化和查询某些内容来执行双重任务),您应该简单地将寄存器列为缓冲区,而不是输出,因此不需要任何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中返回


前三个(后一个没有输出,只有一个
级别
输入)通过RDX销毁RAX,而不告诉编译器。它将假设这些寄存器仍然保存着它保存在其中的任何内容。它们显然不可用


asm(“CPUID”):“=a”(eax)、“=b”(ebx)、“=c”(ecx)、“=d”(edx):“0”(级别):“内存”);
没有
易失性的
)如果不使用任何输出,将进行优化。如果使用它们,它仍然可以从循环中提升。非
volatile
asm语句被优化器视为纯函数,没有任何副作用

它有一个记忆障碍,但是(我想)这并不能阻止它进行优化,它只是意味着,如果/when/where确实运行,它可能读/写的任何变量都会同步到内存中,因此内存内容与C抽象机在该点上的内容相匹配。不过,这可能会排除尚未获取地址的局部变量

asm(“::“memory”)
std::atomic\u thread\u fence(std::memory\u order\u seq\u cst)
非常相似,但请注意,
asm
语句没有输出,因此隐式
易失性
。这就是为什么它没有被优化的原因,而不是因为
“memory”
本身(
volatile
)带有内存阻塞器的asm语句是编译器的一道屏障,可以防止对加载或存储进行重新排序。

优化器根本不关心第一个字符串文本中的内容,只关心约束/缓冲区,因此
asm volatile(“任何”:::register clobbers,“memory”)
也是一个仅编译时的内存屏障。我假设这是您想要的,来序列化一些内存操作


“0”(level)
是第一个操作数的匹配约束(
“=a”
)。您同样可以编写
“a”(level)
,因为在这种情况下,编译器无法选择要选择的寄存器;输出约束只能由
eax
来满足。您还可以使用
“+a”(eax)
作为输出操作数,但您必须在asm语句之前设置
eax=level
。对于x87堆栈内容,有时需要匹配约束而不是读写操作数;我认为这在SO问题中出现过一次。但除了像这样奇怪的东西之外,优点是能够使用不同的C变量输入和输出,或根本不使用变量进行输入。(例如,文字常量或左值(表达式))

无论如何,告诉编译器提供输入可能会导致一条额外的指令,例如,
level=0
将导致
xor
-对
eax
进行零化。如果之前的任何操作都不需要零寄存器,这将是对指令的浪费。通常,对输入进行异或零化会破坏依赖项cy在上一个值上,但CPUID的整个要点是它正在序列化,因此它必须等待所有以前的指令完成执行。确保
eax
提前准备好是毫无意义的;如果您不关心输出,甚至不要告诉编译器您的asm语句接受输入使得在没有开销的情况下使用未定义/未初始化的值变得困难或不可能;有时使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__