簡體   English   中英

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

[英]C++ fast division/mod by 10^x

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

例如:

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

要么:

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

如果我要使用正確的位移>> ,那么我將獲得2^x模式,這不是我想要的。

有什么辦法可以加速整數除法和mod函數的程序嗎?

簡答: 沒有

答案:不。

說明:
編譯器已經為您優化了這樣的語句。
如果有一種技術可以比整數除法更快地實現它,那么編譯器已經知道它並將應用它(假設你打開優化)。

如果您提供適當的體系結構標志,那么編譯器甚至可能知道特定的快速體系結構特定的組件,這將為執行操作提供一個很好的技巧,否則它將為其編譯的通用體系結構應用最佳技巧。

簡而言之,編譯器將在任何優化技巧中擊敗人類99.9999999%的時間(嘗試記住添加優化標志和體系結構標志)。 所以你通常做的最好的事情就是編譯器。

如果通過一些奇跡,你會發現一個尚未找到的與后端編譯器團隊密切合作的程序集中的方法。 然后請讓他們知道,下一版本的熱門編譯器將通過10個優化技巧更新為'未知(谷歌)'部門。

來自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);

}

這對於缺少任何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位環境中工作,則可以優化大多數這些移位。

換句話說,在匯編程序中編寫正確版本的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

再一次,只是一個注釋。 這更適用於確實存在嚴重分裂的芯片。 在現代處理器和現代編譯器上,部門通常以非常聰明的方式進行優化。

您還可以查看libdivide項目。 在一般情況下,它旨在加速整數除法。

除非你的架構支持二進制編碼的十進制,否則只有大量的程序集混亂。

簡答:這取決於。

答案很長:

是的,如果您可以使用編譯器無法自動推斷的內容,則很有可能。 然而,根據我的經驗,這是非常罕見的; 大多數編譯器現在非常擅長矢量化。 但是,在很大程度上取決於您對數據建模的方式以及您是否願意創建極其復雜的代碼。 對於大多數用戶,我不建議首先解決問題。

舉個例子,這里是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 ,這要慢得多。

知道你的記憶已經對齊,知道你的代碼可以被矢量化,這是非常有益的。 請注意,這確實需要您以可能的方式存儲數據。

例如,如果要計算所有整數的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的其他權力和未簽名的分裂,我建議閱讀本文。

實際上你不需要做任何事情。 編譯器足夠智能,可以使用常量優化乘法/除法。 你可以在這里找到很多例子

你甚至可以快速除以5然后向右移1

如果除數是一個顯式的編譯時常量(即如果你的x在10 ^ x是一個編譯時常量),那么除了語言提供的/%運算符之外,使用其他任何東西絕對沒有意義。 如果有一種有意義的方法可以加速顯示10的顯式冪,那么任何自尊的編譯器都會知道如何做到這一點,並會為你做到這一點。

當您考慮“自定義”實現(除了啞編譯器)之外的唯一情況是x是運行時值。 在這種情況下,你需要某種十進制十進制和類比。 在二進制機器上,加速可能是可能的,但我懷疑你是否能夠實現任何有意義的事情。 (如果數字以二進制十進制格式存儲,則很容易,但在“正常”情況下 - 不。)

如果您的運行時確實由10個x相關操作支配,那么您可以首先使用基數為10的整數表示。

在大多數情況下,我預計所有其他整數操作的減速(以及降低的精度或可能額外的內存使用)將超過更快的10 x操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM