在编译器中,布尔值作为8位进行处理。它们的操作效率低吗?
在编译器中,布尔值作为8位进行处理。它们的操作效率低吗?
我正在阅读Agner Fog的《优化C++软件》(专门针对Intel、AMD和VIA的x86处理器),书中在第34页中提到:
布尔变量被存储为8位整数,值为0表示假,值为1表示真。
布尔变量是过度确定的,即所有以布尔变量作为输入的运算符都会检查输入是否为0或1以外的其他值,但以布尔变量作为输出的运算符只能产生0或1。这使得以布尔变量作为输入的操作不够高效。
这个说法今天还适用吗?在哪些编译器上适用?你能举个例子吗?作者提到:
如果可以确定操作数只有0和1这两个值,布尔运算可以更高效地执行。编译器不做这种假设的原因是变量可能未初始化或来自未知来源,因此可能具有其他值。
这是否意味着,如果我拿到一个函数指针bool(*)()
并调用它,那么对它进行的操作会产生低效的代码?或者当我通过解引用指针或从引用中读取布尔值后再对其进行操作时会出现这种情况?
在编译器中,布尔值在编译器中被表示为8位,这会导致对它们进行的操作效率低下。通过编译器生成的代码可以看出,当声明一个函数为bool类型时,编译器会假设参数a和b只能是0或1,即有效的布尔值。如果调用者使用其他类型(如int)作为参数调用该函数时,编译器会进行类型转换。这种类型转换会导致代码中出现testl+setne的操作,将非0的值转换为1,以进行布尔运算。
解决这个问题的方法是,调用者在调用布尔函数之前,将其他类型的参数转换为布尔值。这样可以避免编译器进行类型转换,提高代码的效率。
总结起来,布尔值在编译器中表示为8位,对它们进行操作的效率较低。为了提高代码的效率,调用者在调用布尔函数之前应将其他类型的参数转换为布尔值。这样可以避免编译器进行类型转换,提高代码的效率。
在编译器中,布尔值作为8位(即一个字节)的形式存在。这样设计的原因是为了提高效率。编译器在处理布尔值时,不会对其进行有效性检查,而是假设其只能为0或1。这样的设计可以在进行布尔运算和将布尔值转换为整数时提供更高的性能。
编译器生成的代码示例显示了这一点。在布尔或运算中,编译器使用简单的“或”操作,而不进行0/1检查。在将布尔值转换为整数时,编译器只是简单地将其移动到目标寄存器中。唯一一种可能导致性能不佳的情况是将字符转换为布尔值,因为这时编译器需要检查字符是否为0,并相应地设置布尔值为0或1。
在AMD64系统中,布尔值在内存中以单字节的形式存储,其值始终为0或1。在整型寄存器中,布尔值的8个字节都是有效的,任何非零值都被视为true。因此,对于遵循AMD64系统的平台,我们可以确定布尔值始终具有0/1的值。
布尔值作为8位在编译器中的使用是高效的。除了字符到布尔值的转换外,在其他操作中,当前的设计方法同样好或者更好。对于遵循SysV ABI的平台,我们可以确保布尔值具有0/1的值。至于MSVC的ABI规范,目前没有找到关于布尔值的具体说明。
因此,从上述内容来看,可以得出结论,编译器在处理布尔值时,使用的是总是包含0/1的方式,而不进行有效性检查。布尔值作为8位在编译器中的使用是高效的,除了字符到布尔值的转换外,其他操作都同样好或者更好。
TL:DR: 目前的编译器在处理布尔值时仍存在一些优化问题,特别是在执行类似于`(a&&b) ? x : y`这样的操作时。但问题的根本原因不是编译器没有假设布尔值为0或1,而是编译器在优化这些操作时效果不佳。
许多对布尔值的使用都是用于局部变量或内联函数中,因此将布尔值转换为0或1可以优化掉分支(或者使用cmov等操作)来处理原始条件。只有在必须将布尔值传递/返回给不内联的东西,或者真正存储在内存中时,才需要担心优化布尔值的输入/输出。
可能的优化准则是,将来自外部源(函数参数/内存)的布尔值与位操作符(如a&b)组合在一起。MSVC和ICC对此处理得更好。对于本地布尔值可能无效。请注意,a&b只对布尔值等效,而不对整数类型等效。2&&1为true,但2&1为0,即false。按位或没有这个问题。
对于从函数内的比较中设置的本地布尔值,我不知道这个准则是否会对其产生负面影响(或者是否会导致编译器在可能的情况下实际创建整数布尔值而不仅仅是直接使用比较结果)。此外,请注意,这似乎对当前的gcc和clang没有帮助。
是的,C++在x86上的实现将bool存储在一个始终为0或1的字节中(至少对于编译器必须遵守ABI/调用约定的函数调用边界而言是这样)。编译器有时会利用这一点,例如对于bool->int转换,即使是gcc 4.4也只是零扩展为32位(movzx eax, dil)。Clang和MSVC也是如此。C和C++规则要求此转换产生0或1,因此只有当一个bool函数参数或全局变量始终安全地假定为0或1值时,才能使用这种行为。
即使旧的编译器通常也会利用它来进行bool->int转换,但在其他情况下不会利用它。因此,当Agner说“编译器不做这样的假设的原因是,如果变量未初始化或来自未知源,则变量可能具有其他值。”时是错误的。
MSVC CL19确实会生成代码,假设bool函数参数为0或1,因此Windows x86-64 ABI必须保证这一点。在x86-64 System V ABI(除了Windows之外的所有东西都使用)中,修订版0.98的更改日志中写道“指定_Bool(也称为bool)在调用者处进行布尔化。”我认为即使在那个变化之前,编译器已经假设了这一点,但这只是对编译器已经依赖的内容进行了记录。x86-64 SysV ABI中的当前语言是:
“3.1.2 数据表示
布尔值在存储在内存对象中时,存储为单字节对象,其值始终为0(false)或1(true)。当存储在整数寄存器中(除了作为参数传递之外),寄存器的所有8个字节都是有效的;任何非零值都被视为true。”
关于i386 System V ABI中的语言也是一样的。
任何一个编译器如果在一个方面假设了0/1(例如转换为int),但在其他情况下没有利用它,就是一个未经优化。不幸的是,这样的未经优化仍然存在,尽管比Agner写那段关于编译器“总是”重新布尔化的段落时要少见得多。