通常在内部循环中,我需要以“环绕”的方式索引数组,以便(例如)如果数组大小为100,而我的代码要求元素-2,则应将元素98指定给它。在许多高级语言(如Python)中,可以简单地使用my_array[index % array_size]
完成此操作,但由于某些原因,C的整数运算(通常)向零舍入,而不是始终向下舍入,因此当给定负的第一个参数时,其模运算符返回负的结果。
通常我知道index
不会小于-array_size
,在这种情况下,我只执行my_array[(index + array_size) % array_size]
,但是,有时候这是不能保证的,在这种情况下,我想知道实现一个总是正的模函数的最快方法,有几个“聪明”的方法可以在没有分支的情况下实现它,比如
inline int positive_modulo(int i, int n) {
return (n + (i % n)) % n;
}
或
inline int positive_modulo(int i, int n) {
return (i % n) + (n * (i < 0));
}
当然,我可以分析这些来找出哪个是我的系统上最快的,但我不禁担心我可能错过了一个更好的,或者我的机器上最快的东西可能在另一个机器上很慢。
那么有没有一个标准的方法来做到这一点,或者一些聪明的技巧,我错过了,这可能是最快的可能的方式?
另外,我知道这可能是一厢情愿的想法,但如果有一种方法可以做到这一点,可以自动矢量化,这将是惊人的。
9条答案
按热度按时间8i9zcol21#
我学习的标准方法是
这个函数实际上是第一个没有
abs
的变量(实际上,abs
会使它返回错误的结果),如果一个优化编译器能够识别这种模式并将其编译成计算"无符号模"的机器码,我不会感到惊讶。编辑:
接下来是第二个变体:首先,它也包含一个bug--
n < 0
应该是i < 0
。这个变体看起来不像是分支,但是在很多架构上,
i < 0
会编译成一个条件跳转,无论如何,用i < 0? n: 0
替换(n * (i < 0))
至少会一样快,这样就避免了乘法;此外,它更"干净",因为它避免了将bool重新解释为int。至于这两个变体中哪一个更快,这可能取决于编译器和处理器的架构--给这两个变体计时,然后再看。
cyej8jka2#
以2的幂为模,如下所示(假设以二进制补码表示):
vltsax253#
大多数时候,编译器非常擅长优化您的代码,因此通常最好保持代码的可读性(以便编译器和其他开发人员都知道您在做什么)。
由于数组大小总是正数,我建议你把商定义为
unsigned
,编译器会把小的if/else块优化成没有分支的条件指令:这将创建一个非常小的函数,没有分支:
例如,
modulo(-5, 7)
返回2
。不幸的是,由于商是未知的,所以它们必须执行整数除法,这与其他整数运算相比有点慢。如果你知道数组的大小是2的幂,我建议把这些函数定义放在头文件中,这样编译器就可以把它们优化成一个更有效的函数。下面是函数
unsigned modulo256(int v) { return modulo(v,256); }
:参见组装:https://gcc.godbolt.org/z/DG7jMw
查看与投票最多的答案的比较:http://quick-bench.com/oJbVwLr9G5HJb0oRaYpQOCec4E4
编辑:事实证明,Clang能够生成一个函数,而不需要任何条件移动指令(这比常规算术运算的开销更大)。这种差异在一般情况下是完全可以忽略不计的,因为整数除法大约占用总时间的70%。
基本上,Clang将
value
右移以将其符号位扩展到m
的整个宽度(即,当为负时为0xffffffff
,否则为0
),该宽度用于屏蔽mod + m
中的第二操作数。mwyxok5s4#
使用二进制补码符号位传播获取可选加数的一种老派方法:
tjjdgumg5#
在C/C ++中获取正数模的最快方法
下面的代码快吗?--可能没有其他代码快,但是对于all1
a,b
来说,它简单而且功能正确--不像其他代码。[Edit 2022年]
从here开始,添加了处理
INT_MIN mod -1
和检测mod 0
的测试。各种其他答案都有
mod(a,b)
的弱点,尤其是当b < 0
。有关
b < 0
的更多信息,请参见Euclidean division当
i % n + n
溢出时失败(假设i, n
较大)-未定义的行为。依赖于
n
作为2的幂。(公平地说,答案确实提到了这一点。)当
n < 0
时经常失败。例如,positive_mod(-2,-3) --> -5
必须使用2个整数宽度。(答案确实提到了这一点。)
modulo < 0
失败。positive_modulo(2, -3)
--〉-1。当
n < 0
时经常失败。例如,positive_modulo(-2,-3) --> -5
1例外情况:在C中,当
a/b
溢出时,a%b
没有定义,如a/0
或INT_MIN/-1
。c7rzv4ha6#
如果你可以升级到一个更大的类型(并且在更大的类型上进行模运算),这段代码只进行一次模运算,没有if:
xxb16uws7#
如果您想避免所有条件路径(包括上面生成的条件移动,(例如,如果您需要此代码进行矢量化,或在恒定时间内运行),您可以使用符号位作为掩码:
这是quickbench results你可以看到gcc在泛型情况下更好,对于clang在泛型情况下速度是一样的,因为clang在泛型情况下生成无分支代码,无论如何,这个技术都是有用的,因为编译器不能总是产生特定的优化,你可能需要手工滚动它来生成向量代码。
lkaoscv78#
也可以执行
array[(i+array_size*N) % array_size]
,其中N是一个足够大的整数,可以保证参数为正,但又足够小,不会溢出。当array_size为常数时,有一些方法可以计算模数而不用除法,除了2的幂方法外,还可以计算位组乘以2^i % n的加权和,其中i是每组中的最低有效位:
例如,32位整数0xaabbccdd%100 = dd + cc*[2]56 + bb*[655]36 + aa*[167772]16,具有(1+56+36+16)*255 = 27795的最大范围。通过重复应用和不同的细分,可以将运算减少到很少的条件减法。
常见的做法还包括倒数为2^32 / n的除法近似,这通常可以处理相当大范围的参数。
tyky79it9#
你的第二个例子比第一个例子要好,乘法运算比if/else运算复杂,所以使用这个例子: