通常在我的内部循环中,我需要以"回绕"的方式索引一个数组,这样(例如)如果数组大小是my_array[index % array_size],并且我的代码要求元素-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));
}

Of course I can profile these to find out which is the fastest on my system, but I can't help w或rying that I might have missed a better one, 或 that what's fast on my machine might be slow on a different one.

So is there a standard way to do this, 或 some clever trick that I've missed that's likely to be the fastest possible way?

Also, I know it's probably wishful thinking, but if there's a way of doing this that can be auto-vect或ised, that would be amazing.

推荐答案

大多数情况下,编译器非常擅长优化代码,因此通常最好保持代码的可读性(让编译器和其他开发人员都知道您在做什么).

由于数组大小始终为正,我建议您将商定义为unsigned.编译器将把小的if/else块优化为没有分支的条件指令:

unsigned modulo( int value, unsigned m) {
    int mod = value % (int)m;
    if (mod < 0) {
        mod += m;
    }
    return mod;
}

这创建了一个非常小的函数,没有分支:

modulo(int, unsigned int):
        mov     eax, edi
        cdq
        idiv    esi
        add     esi, edx
        mov     eax, edx
        test    edx, edx
        cmovs   eax, esi
        ret

例如,modulo(-5, 7)返回2.

不幸的是,由于商是未知的,它们必须执行整数除法,这与其他整数运算相比有点慢.如果您知道数组的大小是2的幂,我建议将这些函数定义保存在头中,以便编译器可以将它们优化为更高效的函数.下面是函数unsigned modulo256(int v) { return modulo(v,256); }:

modulo256(int):                          # @modulo256(int)
        mov     edx, edi
        sar     edx, 31
        shr     edx, 24
        lea     eax, [rdi+rdx]
        movzx   eax, al
        sub     eax, edx
        lea     edx, [rax+256]
        test    eax, eax
        cmovs   eax, edx
        ret

见大会:https://gcc.godbolt.org/z/DG7jMw

参见与多数投票结果的比较:http://quick-bench.com/oJbVwLr9G5HJb0oRaYpQOCec4E4

Benchmark comparison

编辑:事实证明,Clang能够在没有任何条件移动指令的情况下生成函数(这比常规算术运算的成本更高).这种差异在一般情况下是完全可以忽略的,因为整除约占总时间的70%.

基本上,Clang右移value以将其符号位扩展到m的整个宽度(即,负时为0xffffffff,否则为0),用于屏蔽mod + m中的第二个操作数.

unsigned modulo (int value, unsigned m) {
    int mod = value % (int)m;
    m &= mod >> std::numeric_limits<int>::digits;
    return mod + m;
}

C++相关问答推荐

intellisense不工作,甚至已经下载了c/c++扩展

无效使用未定义类型'structsquare'?

C指针地址和转换

找出文件是否包含给定的文件签名

如果实际的syscall是CLONE(),那么为什么strace接受fork()呢?

编译的时候g++通常会比GCC慢很多吗?

双指针指向常量双指针的指针类型赋值不兼容

我的程序在收到SIGUSR1信号以从PAUSE()继续程序时总是崩溃()

错误Cygwin_Except::Open_stackdupfile:正在转储堆栈跟踪是什么?

S的这种管道实施有什么问题吗?

在C中创建任意类型的只读指针参数

Caesar密码调试:输出文本末尾的问号和随机字符

C语言中MPI发送接收字符串时出现的分段错误

基于蝶数恰好有8个除数的事实的代码

即使我在C++中空闲,也肯定会丢失内存

是否有单独的缓冲区用于读写库调用?

未为同一文件中的函数执行DirectFunctionCall

即使客户端不发送数据,也会发生UNIX套接字读取

使用fread()函数读取txt文件

将帧从相机 (/dev/video0) 复制到帧缓冲区 (/dev/fb0) 会产生意外结果