C ++快速除法/ mod乘10 ^ x

时间:2010-01-09 11:44:45

标签: c++

在我的程序中,我使用了很多整数除以10 ^ x和整数mod函数10。

例如:

unsigned __int64 a = 12345;
a = a / 100;
....

或:

unsigned __int64 a = 12345;
a = a % 1000;
....

如果我要使用正确的位移>>,那么我将获得2^x的模式,这不是我想要的。

有没有办法可以加速整数除法和mod函数的程序?

10 个答案:

答案 0 :(得分:29)

简答:

长答案: NO。

说明:
编译器已经为你优化了这样的语句 如果有一种技术可以比整数除法更快地实现它,那么编译器已经知道它并将应用它(假设你启用了优化)。

如果您提供适当的体系结构标志,那么编译器甚至可能知道特定的快速体系结构特定的组件,这将为操作提供一个很好的技巧,否则它将为其编译的通用体系结构应用最佳技巧。

简而言之,编译器将在任何优化技巧中击败人类99.9999999%的时间(尝试记住添加优化标志和架构标志)。所以你通常做的最好的事情就是编译器。

如果通过一些奇迹发现了一个尚未找到的与后端编译器团队密切合作的程序集中的方法。然后请让他们知道,下一版本的热门编译器将通过10个优化技巧更新“未知(谷歌)”部门。

答案 1 :(得分:14)

来自http://www.hackersdelight.org/divcMore.pdf

unsigned divu10(unsigned n) {
unsigned q, r;
q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q >> 3;
r = n - q*10;
return q + ((r + 6) >> 4);

}

答案 2 :(得分:7)

这对于缺少任何div操作的环境非常有用,并且它比我的i7上的原生分区慢2倍(自然地优化了)。

这是一个稍微快一点的算法版本,但仍有一些令人讨厌的舍入错误与负数。

static signed Div10(signed n)
{
    n = (n >> 1) + (n >> 2);
    n += n < 0 ? 9 : 2;
    n = n + (n >> 4);
    n = n + (n >> 8);
    n = n + (n >> 16);
    n = n >> 3;
    return n;
}

由于此方法适用于32位整数精度,因此如果您在8位或16位环境中工作,则可以优化大部分这些移位。

答案 3 :(得分:5)

另一方面,在汇编程序中编写Div#n#的正确版本可能更有意义。编译器不能总是有效地预测最终结果(尽管在大多数情况下,他们做得相当好)。因此,如果您在低级别的微芯片环境中运行,请考虑手写asm例程。

#define BitWise_Div10(result, n) {      \
    /*;n = (n >> 1) + (n >> 2);*/           \
    __asm   mov     ecx,eax                 \
    __asm   mov     ecx, dword ptr[n]       \
    __asm   sar     eax,1                   \
    __asm   sar     ecx,2                   \
    __asm   add     ecx,eax                 \
    /*;n += n < 0 ? 9 : 2;*/                \
    __asm   xor     eax,eax                 \
    __asm   setns   al                      \
    __asm   dec     eax                     \
    __asm   and     eax,7                   \
    __asm   add     eax,2                   \
    __asm   add     ecx,eax                 \
    /*;n = n + (n >> 4);*/                  \
    __asm   mov     eax,ecx                 \
    __asm   sar     eax,4                   \
    __asm   add     ecx,eax                 \
    /*;n = n + (n >> 8);*/                  \
    __asm   mov     eax,ecx                 \
    __asm   sar     eax,8                   \
    __asm   add     ecx,eax                 \
    /*;n = n + (n >> 16);*/                 \
    __asm   mov     eax,ecx                 \
    __asm   sar     eax,10h                 \
    __asm   add     eax,ecx                 \
    /*;return n >> 3;}*/                    \
    __asm   sar     eax,3                   \
    __asm   mov     dword ptr[result], eax  \
}

用法:

int x = 12399;
int r;
BitWise_Div10(r, x); // r = x / 10
// r == 1239

再次,只是一个注释。这更适用于确实存在严重分裂的芯片。在现代处理器和现代编译器上,分部通常以非常聪明的方式进行优化。

答案 4 :(得分:2)

除非您的体系结构支持二进制编码的十进制,否则只会出现大量的程序集混乱。

答案 5 :(得分:2)

您还可以查看libdivide项目。在一般情况下,它旨在加速整数除法。

答案 6 :(得分:2)

简答:这取决于。

长答案:

是的,如果您可以使用编译器无法自动推断的内容,则很有可能。然而,根据我的经验,这是非常罕见的;大多数编译器现在非常擅长矢量化。但是,在很大程度上取决于您对数据建模的方式以及您是否愿意创建极其复杂的代码。对于大多数用户,我不建议首先解决问题。

举个例子,这里是x / 10的实现,其中x是有符号整数(这实际上是编译器将生成的):

int eax = value * 0x66666667;
int edx = ([overflow from multiplication] >> 2); // NOTE: use aritmetic shift here!
int result = (edx >> 31) + edx;

如果您反汇编已编译的C ++代码,并且为'10'使用常量,它将显示反映上述内容的汇编代码。如果你没有使用常量,它将生成idiv,这要慢得多。

知道你的记忆是对齐的c.q.知道你的代码可以被矢量化,这是非常有益的。请注意,这确实需要您以可能的方式存储数据。

例如,如果你想计算所有整数的sum-of-div / 10,你可以这样做:

    __m256i ctr = _mm256_set_epi32(0, 1, 2, 3, 4, 5, 6, 7);
    ctr = _mm256_add_epi32(_mm256_set1_epi32(INT32_MIN), ctr);

    __m256i sumdiv = _mm256_set1_epi32(0);
    const __m256i magic = _mm256_set1_epi32(0x66666667);
    const int shift = 2;

    // Show that this is correct:
    for (long long int i = INT32_MIN; i <= INT32_MAX; i += 8)
    {
        // Compute the overflow values
        __m256i ovf1 = _mm256_srli_epi64(_mm256_mul_epi32(ctr, magic), 32);
        __m256i ovf2 = _mm256_mul_epi32(_mm256_srli_epi64(ctr, 32), magic);

        // blend the overflows together again
        __m256i rem = _mm256_srai_epi32(_mm256_blend_epi32(ovf1, ovf2, 0xAA), shift);

        // calculate the div value
        __m256i div = _mm256_add_epi32(rem, _mm256_srli_epi32(rem, 31));

        // do something with the result; increment the counter
        sumdiv = _mm256_add_epi32(sumdiv, div);
        ctr = _mm256_add_epi32(ctr, _mm256_set1_epi32(8));
    }

    int sum = 0;
    for (int i = 0; i < 8; ++i) { sum += sumdiv.m256i_i32[i]; }
    std::cout << sum << std::endl;

如果您对两种实现进行基准测试,您会发现在Intel Haswell处理器上,您将获得以下结果:

  • idiv:1,4 GB / s
  • 编译器优化:4 GB / s
  • AVX2指令:16 GB / s

对于其他10级和无符号分区的权力,我建议阅读本文。

答案 7 :(得分:1)

如果除数是一个显式的编译时常量(即如果你的x在10 ^ x中是一个编译时常量),那么使用语言提供的{{1}绝对没有意义}和/运算符。如果有一种有意义的方法可以加速显示10的显式幂,那么任何自尊的编译器都会知道如何做到这一点,并会为你做到这一点。

当您考虑“自定义”实现(除了愚蠢的编译器)之外的唯一情况是%是运行时值。在这种情况下,您需要某种十进制移位十进制和类比。在二进制机器上,加速可能是可能的,但我怀疑你是否能够实现任何有意义的事情。 (如果数字以二进制十进制格式存储,则很容易,但在“正常”情况下 - 不。)

答案 8 :(得分:0)

实际上你不需要做任何事情。编译器足够智能,可以使用常量优化乘法/除法。你可以在这里找到很多例子

您甚至可以快速divide by 5然后向右移动1

答案 9 :(得分:0)

如果您的运行时确实由10个 x 相关的操作支配,那么可以首先使用基数为10的整数表示。

在大多数情况下,我预计所有其他整数运算的减速(以及降低的精度或可能额外的内存使用)将超过更快的10 x 运营。