无符号数学是否需要更多CPU指令?

时间:2011-07-10 12:02:27

标签: c++ cpu unsigned

获取C ++积分变量i,并假设您将其值乘以2.

如果i有签名,我相信该操作在某种程度上是等效的,至少在数学上,对于:

i = i << 1;

但是如果i的类型是无符号的,那么因为无符号值不会溢出但是以模块的范围执行,大概是操作是这样的:

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

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

(是的,在编程时关心这一点会很荒谬;出于纯粹的好奇心,我很感兴趣。)

7 个答案:

答案 0 :(得分:11)

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

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

左移也没有区别:硬件只丢弃最左边的位,在你的例子中不需要按位执行。

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

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

答案 1 :(得分:11)

正如其他人已经写过的那样:对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;
}

酒吧会保持不变。

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

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

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

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

答案 2 :(得分:2)

假设环绕溢出,这是两个补码硬件上的大多数(全部?)CPU算术指令无论如何,<<对于无符号类型等同于乘法。因此,唯一的问题是,当您使用比用于包含它的寄存器小的类型进行算术运算时。

有关在算术表达式中至少提升int(或unsigned int)的规则,其设计目的是为了避免这种情况发生:当你将unsigned short乘以2时结果是int(如果unsigned intshort大小相同,则为int。无论哪个,当寄存器大小与类型匹配时,不需要采用任何模数。使用二进制补码,无论如何都不需要有符号和无符号C ++乘法的不同指令:除非硬件提供溢出位并且您关心它的值(-1 * 2将是无符号溢出,但不是有符号溢出,即使结果位模式相同)。

可能需要掩码的唯一时间是/当您将结果转换回unsigned short时。即便如此,我认为实施有时可以在用于保存中间int值的unsigned short大小的寄存器的顶部留下额外的“不相关”位。您知道那些额外的顶部位不会影响加法,乘法或减法的模数结果,并且如果将值存储到存储器中它们将被屏蔽掉(假设一条指令存储{{1的底部2个字节)大小寄存器到2个字节的内存,模数基本上是免费的)。因此,在分割,右移,比较以及我忘记的任何其他内容之前,必须小心掩盖实现,否则使用适当的指令(如果可用)。

答案 3 :(得分:1)

据我所知,大多数CPU都在硬件中拥有未签名的操作,我很确定x86会这样做。

答案 4 :(得分:1)

无符号数学确实溢出,因此隐含地模数它们各自的范围。

答案 5 :(得分:1)

interjay的答案涵盖了基础知识。更多细节:

  

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

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

后来,当芯片变大并且参数的操作码中有更多位时,实现了“桶形移位器”,它可以在一个周期内移位任意数量的位。这就是现代CPU的用途。

  

或者,与带有符号值的数学相比,使用无符号值执行数学运算往往会花费额外的指令吗?

从不。当无符号和有符号的操作不同时,每个操作都会有单独的指令。

答案 6 :(得分:1)

我认为你的方法是错误的:在无符号数据类型上,位移完全与它在锡上所说的完全相同,未占用的位用零填充。这会导致对左移的类型值进行正确的模运算操作,即乘法。右移没有算术类比,因为Z / nZ一般不是一个除法环,也没有除法概念;右移只是截断分裂。

另一方面, signed 类型存在歧义,因为有不同的方法将位模式解释为有符号整数。通过左移2的补码,你将获得乘法的预期“环绕”,但没有规范的右移行为选择。在旧的C标准中,我认为这是完全实现定义的,但我认为C99使这种行为具体化。

相关问题