在单CPU指令中可以在0和1之间翻转位/整数/布尔值的任何可能代码

Bhu*_*bey 3 c c++ x86 assembly micro-optimization

单个x86指令可以在"0"和"1"之间切换布尔值吗?

我想到了以下方法,但都导致了两个指令与-cc标志的gcc.

status =! status;

status = 1 - status;

status  = status == 0 ? 1: 0;

int flip[2] = {1, 0};
status = flip[status];
Run Code Online (Sandbox Code Playgroud)

有更快的方法吗?

这就是我尝试过的:https://godbolt.org/g/A3qNUw


我需要的是一个切换输入和返回的函数,以编译为一条指令的方式编写.与此功能类似的东西:

int addOne(int n) { return n+1; }
Run Code Online (Sandbox Code Playgroud)

将Godbolt编译为:

  lea eax, [rdi+1]    # return n+1 in a single instruction
  ret
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 8

要翻转整数,请使用xor如下: foo ^= 1.

gcc已经知道了这个优化bool,所以你可以return !status;像普通人一样不失效率.gcc status ^= 1也会编译为xor指令.实际上,除了表查找之外,您的所有想法都会编译为xor具有bool输入/返回值的单个指令.

检查出来的Godbolt编译探险gcc -O3,与ASM输出窗格的boolint.

MYTYPE func4(MYTYPE status) {
    status ^=1;
    return status;
}

  # same code for bool or int
  mov eax, edi
  xor eax, 1
  ret
Run Code Online (Sandbox Code Playgroud)

MYTYPE func1(MYTYPE status) {
    status = !status;
    return status;
}

  # with -DMYTYPE=bool
  mov eax, edi
  xor eax, 1
  ret

  # with int
  xor eax, eax
  test edi, edi
  sete al
  ret
Run Code Online (Sandbox Code Playgroud)

为什么bool不同int

x86-64 System V ABI要求调用者传递一个bool0或1的值,而不是任何非零整数.因此,编译器可以假设关于输入.

但是int foo,C表达式!foo需要"布尔化"该值. !foo具有类型_Bool/(又名bool如果你#include <stdbool.h>),并转换该回的整数必须产生0或1的值.如果编译器不知道foo必须是0或者1,它不能优化!foofoo^=1,并且可以没有意识到foo ^= 1在truthy/falsy之间翻转一个值.(在C语言中if(foo)意味着if(foo != 0)).

这就是为什么你得到test/setcc(int通过在之前调零xor寄存器将零扩展为32位test)的原因.

相关: 编译器中的布尔值为8位.对他们的操作是否效率低下?.类似的东西(bool1 && bool2) ? x : y并不总是像你希望的那样有效地编译.编译器非常好,但确实有错过优化错误.


那个额外的mov指令怎么样?

如果编译器不需要/想要保留旧的未翻转值以供以后使用,它将在内联时消失.但是在一个独立的函数中,第一个arg处于edi,并且返回值需要在eax(在x86-64 System V调用约定中).

像这样的微小函数非常接近你可能得到的大函数的一部分(如果这个翻转不能被优化成其他东西),但是需要将结果放在不同的寄存器中是一个混淆因素.


x86没有copy-and-xor整数指令,因此对于独立函数,至少mov需要从arg传递寄存器复制到eax.

lea很特别:它是为数不多的整数ALU指令之一,它可以将结果写入不同的寄存器而不是破坏其输入. lea是一个复制和移位/添加指令,但在x86中没有copy-and-xor指令.许多RISC指令集具有3操作数指令,例如MIPS可以执行xor $t1, $t2, $t3.

AVX引入了矢量指令的非破坏性版本(在很多代码中保存了很多movdqa/ movups寄存器复制),但是对于整数,只有少数新指令可以执行不同的操作. rorx eax, ecx, 16 例如eax = rotate_right(ecx, 16),并使用非破坏性AVX指令使用的相同VEX编码.


归档时间:

查看次数:

764 次

最近记录:

7 年,10 月 前