无符号数学需要更多的CPU指令吗?

Does unsigned math require more CPU instructions?

本文关键字:CPU 指令 无符号      更新时间:2023-10-16

取一个c++积分变量i,并假设将其值乘以2。

如果i有签名,我相信这个操作在某种程度上相当于,至少在数学上,等于:

i = i << 1;

但是如果i的类型是无符号的,那么由于无符号值不会溢出,而是对它们的范围进行模运算,大概操作是这样的:

i = (i << 1) & (decltype(i))-1;

现在,我认为实际的机器指令可能比乘法的移位序列更简洁。但是,现代的CPU,比如x86,会有针对无符号/模运算的特定指令吗?或者,与使用有符号值进行数学运算相比,使用无符号值进行数学运算是否会花费额外的指令?

(是的,在编程时关心这个是荒谬的;)

正如其他人已经写的那样:这对CPU无关紧要。有符号指令和无符号指令花费的时间相同,无符号算术中的一些操作甚至更容易完成,可能比有符号算术中的一些操作需要更少的周期(多精度除法就是一个例子)。

然而,这只是故事的一半。

c++将有符号整数溢出定义为未定义行为,将无符号整数定义为modulo2。这提供了完全不同的优化机会,从而产生不同的代码。

一个例子:

int foo (int a)
{
  return a * 1000 / 500;
}
unsigned bar (unsigned a)
{
  return a * 1000 / 500;
}

这里foo可以优化为:

int foo (int a)
{
  return a * 2;
}

bar保持不变

请注意,这两个函数在数学上是相同的,但如果参数超过INT_MAX/1000,它们开始给出不同的结果。

由于有符号溢出的影响是未定义的,编译器在简化表达式时可以选择假装没有INT_MAX。对于无符号算术,情况并非如此,编译器必须发出执行乘法和除法的代码。这当然比优化后的版本要慢。

注意:大多数编译器在进行此类优化时都是保守的,只有在您要求时才启用它们,因为它们往往会破坏代码和溢出检查。其他编译器,特别是嵌入式和DSP领域的编译器,即使在较低的优化级别上也总是进行这种优化。为这类机器编写程序的程序员知道细微的细节,所以这很少是一个问题。

OTOH我们已经讨论过C/c++程序员在stackoverflow上不止一次陷入这个陷阱的故事。

不,它不需要更多指令,至少在x86上。

对于某些操作(例如加法和减法),有符号和无符号类型都使用相同的指令。这是可能的,因为它们在使用2的补码表示有符号值时工作相同。

左移也没有区别:最左边的位直接被硬件丢弃,不需要按位执行操作——就像在您的示例中一样。

对于其他操作(例如右移),有单独的指令:SHR(右移)用于无符号值,SAR(右移算术)用于有符号值,保留符号位。

对于有符号/无符号的乘法和除法也有单独的指令:MUL/IMUL和DIV/IDIV,其中IMUL和IDIV用于有符号值。

我想你把它弄错了:在无符号数据类型上,位移位完全按照它在tin上所说的做,未占用的位被0填充。这会对类型的值进行正确的模算术运算,用于左移,即乘法。右移没有算术上的类比因为Z/nZ一般不是除法环也没有除法的概念;右移只是截断除法。

另一方面,有符号类型存在歧义,因为将位模式解释为有符号整数有不同的方法。在2的补码上进行左移,您将得到乘法所期望的"绕行",但是没有右移行为的规范选择。在旧的C标准中,我相信这是完全实现定义的,但我认为C99使这种行为具体化。

假设环绕式溢出,哪个是最(全部)?CPU的算术指令对两个的补码硬件做反正,<<对于无符号类型的乘法是等效的。因此,唯一的问题是,当你对一个小于它所使用的寄存器的类型进行算术运算时,

算术表达式中提升到至少int(或unsigned int)的规则基本上是为了避免这种情况发生:当你将unsigned short乘以2时,结果是int(如果shortint大小相同,则为unsigned int)。无论是哪种类型,当寄存器大小与类型匹配时,都不需要进行任何模数运算。对于2的补码,无论如何,你不需要不同的指令来进行有符号和无符号的c++乘法运算:除非硬件提供了一个溢出位并且你关心它的值(-1 * 2将是一个无符号溢出,但不是一个有符号溢出,即使结果的位模式是相同的),否则环绕溢出覆盖两者。

唯一可能需要掩码的时候是当你将结果转换回unsigned short时。即便如此,我认为,谨慎的实现有时可以在int大小的寄存器顶部留下额外的"无关"位,用于保存中间unsigned short值。你知道那些额外的顶部位不会影响加法、乘法或减法的模结果,如果值被存储到内存中,它们将被掩盖(假设一条指令将int大小的寄存器的底部2个字节存储到2个字节的内存中,模量基本上是免费的)。所以这个实现必须小心地在除法、右移、比较和其他我忘记的东西之前进行掩码,或者使用适当的指令。

据我所知,大多数cpu在硬件上都有无符号操作,我很确定x86有。

Unsigned math没有溢出,因此隐式地对它们各自的范围取模。

interjay的回答涵盖了基本内容。更多细节:

现在,我认为实际的机器指令可能会比乘法的移位序列更简洁。

这取决于处理器。在过去,当晶体管很昂贵的时候,像6500和6800这样的处理器每次只能向左或向右移动一位指令。

后来,当芯片变得更大,在一个操作码中有更多的比特用于参数时,"桶移位器"被实现,它可以在一个周期内移动任意数量的比特。这是现代cpu使用的。

或者,与有符号值的数学运算相比,使用无符号值进行数学运算是否会花费额外的指令?

从来没有。

当一个操作在无符号操作和有符号操作之间不同时,每个操作将有单独的指令。