C / C#的模量越快?

时间:2012-06-14 20:11:03

标签: c# math optimization modulus

对于特定碱基,是否有创建比标准%运算符更快的整数模数的技巧?

对于我的节目,我会寻找大约1000-4000(例如n%2048)。是否有更简单的方法来执行n模数2048而不仅仅是:n%2048

7 个答案:

答案 0 :(得分:34)

如果分母在编译时被称为2的幂,就像2048的例子那样,你可以减去1并按位进行 - 和

那是:

n % m == n & (m - 1) 

...其中m是2的幂。

例如:

22 % 8 == 22 - 16 == 6

         Dec   Bin
       -----   -----
          22 = 10110
           8 = 01000  
       8 - 1 = 00111 
22 & (8 - 1) =   10110 
               & 00111 
               -------
           6 =   00110

请记住,一个好的编译器会对%进行自己的优化,甚至可能与上述技术一样快。算术运算符往往非常优化。

答案 1 :(得分:13)

对于两个2^n的幂,您所要做的就是将除最后n位之外的所有位清零。

例如(假设32位整数):

x%2相当于x & 0x00000001

x%4相当于x & 0x00000003

一般来说,x % (2^n)等于x & (2^n-1)。写在C中,这将是x & ((1<<n)-1)

这是因为2^nn+1位(右起)给出1。因此,2^n-1会在右侧为您提供n个,在左侧为零。

答案 2 :(得分:2)

您可以将高阶位清零,即

x = 11 = 1011
x%4 = 3 = 0011

所以对于x%4你可以拿最后两位 - 我不知道如果使用负数会发生什么

答案 3 :(得分:2)

Here's a few techniques复制模数运算。

在那些基准测试中,这是最快的(修改后适合您的2048场景)。只要您的“最大”不是数百万且您提到的1000-4000范围内,它对您来说也可能更快:

int threshold = 2048; //the number to mod by
int max = 1000; //the number on the left. Ex: 1000 % 2048
int total = 0;
int y = 0;
for (int x = 0; x < max; x++)
{
    if (y > (threshold - 1))
    {
        y = 0;
        total += x;
    }
    y += 1;
}
return total;

试一试。它在作者的机器上以performed faster进行了各种设置,因此对你来说也应该表现得非常好。

答案 4 :(得分:0)

对无符号整数进行乘法/除法的最快方法是向左或向右移位。 Shift操作直接与CPU命令匹配。例如,3 <&lt;&lt; 2 = 6,而4>&gt; 1 = 2。

您可以使用相同的技巧来计算模块:向左移动一个足够远的整数,以便只剩下剩余的位,然后将其向右移动,以便检查余数值。

另一方面,整数模也作为CPU命令存在。如果整数模运算符在优化版本中映射到此命令,则使用位移技巧不会看到任何改进。

以下代码通过移动足够远而仅剩下最后2位(因为4 = 2 ^ 2)来计算7%4。这意味着我们需要移位30位:

uint i=7;
var modulo=((i<<30)>>30);

结果是3

修改

我刚刚阅读了所有解决方案,建议简单地删除高阶位。它具有相同的效果,但更简单直接。

答案 5 :(得分:0)

如果要除以2的幂的文字,那么答案可能是否:任何体面的编译器都会自动将这些表达式转换为AND运算的变体,这非常接近最优。

答案 6 :(得分:0)

precomputing magic constants在运行时可以实现无分支 -power-of-2模数,以使用乘法 - 加移位来实现除法。

这比我的英特尔酷睿i5上的内置模运算符%大约快2倍。

我感到惊讶的是,它不是更具戏剧性,因为x86 CPU div指令在某些CPU上的64位除法可以有latencies高达80-90个周期,与{{1}相比3个周期和每个1个周期的按位运算。

下面显示的概念和时间证明。 mul是指在单个var上串行执行的模数运算的数量。这是为了防止CPU通过并行化隐藏延迟。

series_len

结果

Intel Core i5(MacBookAir7,2),macOS 10.11.6,clang 8.0.0

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

typedef int32_t s32;
typedef uint32_t u32;
typedef uint64_t u64;

#define NUM_NUMS 1024
#define NUM_RUNS 500
#define MAX_NUM UINT32_MAX
#define MAX_DEN 1024

struct fastdiv {
    u32 mul;
    u32 add;
    s32 shift;
    u32 _odiv;  /* save original divisor for modulo calc */
};

static u32 num[NUM_NUMS];
static u32 den[NUM_NUMS];
static struct fastdiv fd[NUM_NUMS];

/* hash of results to prevent gcc from optimizing out our ops */
static u32 cookie = 0;

/* required for magic constant generation */
u32 ulog2(u32 v) {
    u32 r, shift;
    r =     (v > 0xFFFF) << 4; v >>= r;
    shift = (v > 0xFF  ) << 3; v >>= shift; r |= shift;
    shift = (v > 0xF   ) << 2; v >>= shift; r |= shift;
    shift = (v > 0x3   ) << 1; v >>= shift; r |= shift;
                                            r |= (v >> 1);
    return r;
}

/* generate constants for implementing a division with multiply-add-shift */
void fastdiv_make(struct fastdiv *d, u32 divisor) {
    u32 l, r, e;
    u64 m;

    d->_odiv = divisor;
    l = ulog2(divisor);
    if (divisor & (divisor - 1)) {
        m = 1ULL << (l + 32);
        d->mul = (u32)(m / divisor);
        r = (u32)m - d->mul * divisor;
        e = divisor - r;
        if (e < (1UL << l)) {
            ++d->mul;
            d->add = 0;
        } else {
            d->add = d->mul;
        }
        d->shift = l;
    } else {
        if (divisor == 1) {
            d->mul = 0xffffffff;
            d->add = 0xffffffff;
            d->shift = 0;
        } else {
            d->mul = 0x80000000;
            d->add = 0;
            d->shift = l-1;
        }
    }
}

/* 0: use function that checks for a power-of-2 modulus (speedup for POTs)
 * 1: use inline macro */
#define FASTMOD_BRANCHLESS 0

#define fastdiv(v,d) ((u32)(((u64)(v)*(d)->mul + (d)->add) >> 32) >> (d)->shift)
#define _fastmod(v,d) ((v) - fastdiv((v),(d)) * (d)->_odiv)

#if FASTMOD_BRANCHLESS
#define fastmod(v,d) _fastmod((v),(d))
#else
u32 fastmod(u32 v, struct fastdiv *d) {
    if (d->mul == 0x80000000) {
        return (v & ((1 << d->shift) - 1));
    }
    return _fastmod(v,d);
}
#endif

u32 random32(u32 upper_bound) {
    return arc4random_uniform(upper_bound);
}

u32 random32_range(u32 lower_bound, u32 upper_bound) {
    return random32(upper_bound - lower_bound) + lower_bound;
}

void fill_arrays() {
    int i;
    for (i = 0; i < NUM_NUMS; ++i) {
        num[i] = random32_range(MAX_DEN, MAX_NUM);
        den[i] = random32_range(1, MAX_DEN);
        fastdiv_make(&fd[i], den[i]);
    }
}

void fill_arrays_pot() {
    u32 log_bound, rand_log;
    int i;

    log_bound = ulog2(MAX_DEN);
    for (i = 0; i < NUM_NUMS; ++i) {
        num[i] = random32_range(MAX_DEN, MAX_NUM);
        rand_log = random32(log_bound) + 1;
        den[i] = 1 << rand_log;
        fastdiv_make(&fd[i], den[i]);
    }
}

u64 clock_ns() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*1000000000 + tv.tv_usec*1000;
}

void use_value(u32 v) {
    cookie += v;
}

int main(int argc, char **arg) {
    u64 builtin_npot_ns;
    u64 builtin_pot_ns;
    u64 branching_npot_ns;
    u64 branching_pot_ns;
    u64 branchless_npot_ns;
    u64 branchless_pot_ns;
    u64 t0, t1;
    u32 v;
    int s, r, i, j;
    int series_len;

    builtin_npot_ns = builtin_pot_ns = 0;
    branching_npot_ns = branching_pot_ns = 0;
    branchless_npot_ns = branchless_pot_ns = 0;

    for (s = 5; s >= 0; --s) {
        series_len = 1 << s;
        for (r = 0; r < NUM_RUNS; ++r) {
            /* built-in NPOT */
            fill_arrays();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v /= den[i];
                }
                use_value(v);
            }
            t1 = clock_ns();
            builtin_npot_ns += (t1 - t0) / NUM_NUMS;

            /* built-in POT */
            fill_arrays_pot();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v /= den[i];
                }
                use_value(v);
            }
            t1 = clock_ns();
            builtin_pot_ns += (t1 - t0) / NUM_NUMS;

            /* branching NPOT */
            fill_arrays();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v = fastmod(v, fd+i);
                }
                use_value(v);
            }
            t1 = clock_ns();
            branching_npot_ns += (t1 - t0) / NUM_NUMS;

            /* branching POT */
            fill_arrays_pot();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v = fastmod(v, fd+i);
                }
                use_value(v);
            }
            t1 = clock_ns();
            branching_pot_ns += (t1 - t0) / NUM_NUMS;

            /* branchless NPOT */
            fill_arrays();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v = _fastmod(v, fd+i);
                }
                use_value(v);
            }
            t1 = clock_ns();
            branchless_npot_ns += (t1 - t0) / NUM_NUMS;

            /* branchless POT */
            fill_arrays_pot();
            t0 = clock_ns();
            for (i = 0; i < NUM_NUMS; ++i) {
                v = num[i];
                for (j = 0; j < series_len; ++j) {
                    v = _fastmod(v, fd+i);
                }
                use_value(v);
            }
            t1 = clock_ns();
            branchless_pot_ns += (t1 - t0) / NUM_NUMS;
        }

        builtin_npot_ns /= NUM_RUNS;
        builtin_pot_ns /= NUM_RUNS;
        branching_npot_ns /= NUM_RUNS;
        branching_pot_ns /= NUM_RUNS;
        branchless_npot_ns /= NUM_RUNS;
        branchless_pot_ns /= NUM_RUNS;

        printf("series_len = %d\n", series_len);
        printf("----------------------------\n");
        printf("builtin_npot_ns    : %llu ns\n", builtin_npot_ns);
        printf("builtin_pot_ns     : %llu ns\n", builtin_pot_ns);
        printf("branching_npot_ns  : %llu ns\n", branching_npot_ns);
        printf("branching_pot_ns   : %llu ns\n", branching_pot_ns);
        printf("branchless_npot_ns : %llu ns\n", branchless_npot_ns);
        printf("branchless_pot_ns  : %llu ns\n\n", branchless_pot_ns);
    }
    printf("cookie=%u\n", cookie);
}