簡體   English   中英

分裂的速度更快? 雙打/浮點數/ UInt32 / UInt64? 在C ++ / C中

[英]What is faster on division? doubles / floats / UInt32 / UInt64 ? in C++/C

我做了一些速度測試,以確定在對數字進行乘法或除法時最快的速度。 我必須努力工作以擊敗優化者。 我得到了無意義的結果,例如在2微秒內運行的大量循環,或者乘法與除法的速度相同(如果只是那樣)。

在我最終努力工作以擊敗足夠的編譯器優化之后,同時仍然讓它優化速度,我得到了這些速度結果。 他們可能對別人感興趣?

如果我的測試仍然懸而未決,請告訴我,但要善待,因為我只花了兩個小時寫這個垃圾:P

64 time: 3826718 us
32 time: 2476484 us
D(mul) time: 936524 us
D(div) time: 3614857 us
S time: 1506020 us

使用雙打“乘以除法”似乎是進行除法的最快方法,其次是整數除法。 我沒有測試分裂的准確性。 可能是“正確的划分”更准確嗎? 我不想在這些速度測試結果之后發現,因為我只是在基數為10的常數上使用整數除法,讓我的編譯器為我優化它;)(並且不會破壞它的優化)。

這是我用來獲得結果的代碼:

#include <iostream>

int Run(int bla, int div, int add, int minus) {
    // these parameters are to force the compiler to not be able to optimise away the
    // multiplications and divides :)
    long LoopMax = 100000000;

    uint32_t Origbla32 = 1000000000;
    long i = 0;

    uint32_t bla32 = Origbla32;
    uint32_t div32 = div;
    clock_t Time32 = clock();
    for (i = 0; i < LoopMax; i++) {
        div32 += add;
        div32 -= minus;
        bla32 = bla32 / div32;
        bla32 += bla;
        bla32 = bla32 * div32;
    }
    Time32 = clock() - Time32;

    uint64_t bla64 = bla32;
    clock_t Time64 = clock();
    uint64_t div64 = div;
    for (long i = 0; i < LoopMax; i++) {
        div64 += add;
        div64 -= minus;
        bla64 = bla64 / div64;
        bla64 += bla;
        bla64 = bla64 * div64;
    }
    Time64 = clock() - Time64;

    double blaDMul = Origbla32;
    double multodiv = 1.0 / (double)div;
    double multomul = div;
    clock_t TimeDMul = clock();
    for (i = 0; i < LoopMax; i++) {
        multodiv += add;
        multomul -= minus;
        blaDMul = blaDMul * multodiv;
        blaDMul += bla;
        blaDMul = blaDMul * multomul;
    }
    TimeDMul = clock() - TimeDMul;

    double blaDDiv = Origbla32;
    clock_t TimeDDiv = clock();
    for (i = 0; i < LoopMax; i++) {
        multodiv += add;
        multomul -= minus;
        blaDDiv = blaDDiv / multomul;
        blaDDiv += bla;
        blaDDiv = blaDDiv / multodiv;
    }
    TimeDDiv = clock() - TimeDDiv;

    float blaS = Origbla32;
    float divS = div;
    clock_t TimeS = clock();
    for (i = 0; i < LoopMax; i++) {
        divS += add;
        divS -= minus;
        blaS = blaS / divS;
        blaS += bla;
        blaS = blaS * divS;
    }
    TimeS = clock() - TimeS;

    printf("64 time: %i us  (%i)\n", (int)Time64, (int)bla64);
    printf("32 time: %i us  (%i)\n", (int)Time32, bla32);

    printf("D(mul) time: %i us  (%f)\n", (int)TimeDMul, blaDMul);
    printf("D(div) time: %i us  (%f)\n", (int)TimeDDiv, blaDDiv);
    printf("S time: %i us  (%f)\n", (int)TimeS, blaS);

    return 0;
}

int main(int argc, char* const argv[]) {
    Run(0, 10, 0, 0); // adds and minuses 0 so it doesn't affect the math, only kills the opts
    return 0;
}

有很多方法可以執行某些算術,所以可能沒有一個答案(移位,小數乘法,實際除法,通過對數單位的某些往返等等;這些可能都有不同的相對成本,具體取決於操作數和資源分配)。

讓編譯器使用它具有的程序和數據流信息。

對於適用於x86上的匯編的一些數據,您可能會看到: “AMD和Intel x86處理器的指令延遲和吞吐量”

最快的將完全取決於目標架構。 它看起來像你只對你碰巧在的平台感興趣,從你的執行時間猜測似乎是64位x86,無論是Intel(Core2?)還是AMD。

也就是說,反向浮點乘法在許多平台上都是最快的,但正如你推測的那樣,通常不如浮點除法(兩次舍入而不是一次) - 無論這對你的使用是否重要是一個單獨的問題)。 一般來說,你最好重新安排你的算法使用較少的分數,而不是跳過箍來使分割盡可能高效(最快的分工是你不做的),並確保在你之前進行基准測試花費時間進行優化,因為划分分區的算法很少而且很遠。

此外,如果您有整數源並需要整數結果,請確保在基准測試中包括整數和浮點之間的轉換成本。

由於您對特定計算機上的計時感興趣,因此您應該知道英特爾現在在其優化參考手冊(pdf)中發布此信息。 具體來說,您將對附錄C第3.1節“注冊操作數的延遲和吞吐量”表格感興趣。

請注意,整數除法時間很大程度上取決於所涉及的實際值。 根據該指南中的信息,您的計時程序似乎仍有相當大的開銷,因為您測量的性能比率與英特爾公布的信息不符。

正如Stephen提到的那樣,使用優化手冊 - 但您也應該考慮使用SSE指令。 這些可以在單個指令中進行4或8個分區/乘法。

此外,分區采用單個時鍾周期進行處理是相當普遍的。 結果可能在幾個時鍾周期(稱為延遲)中不可用,但是下一個除法可以在此期間開始(與第一個重疊),只要它不需要第一個的結果。 這是由於CPU中的管道襯里,就像在先前的負載仍在干燥時可以洗更多的衣服一樣。

乘以除法是一種常見的技巧,應該在你的除數不經常改變的地方使用。

您很有可能花費時間和精力使數學運算快速,只是發現內存訪問的速度(當您導航輸入並寫入輸出時)限制了您的最終實施。

我在MSVC 2008上寫了一個有缺陷的測試

double i32Time  = GetTime();
{
    volatile __int32 i = 4;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i /= 61;
        count++;
    }
}
i32Time = GetTime() - i32Time;

double i64Time  = GetTime();
{
    volatile __int64 i = 4;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i /= 61;
        count++;
    }
}
i64Time = GetTime() - i64Time;


double fTime    = GetTime();
{
    volatile float i = 4;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i /= 4.0f;
        count++;
    }
}
fTime   = GetTime() - fTime;

double fmTime   = GetTime();
{
    volatile float i = 4;
    const float div = 1.0f / 4.0f;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i *= div;
        count++;
    }
}
fmTime  = GetTime() - fmTime;

double dTime    = GetTime();
{
    volatile double i = 4;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i /= 4.0f;
        count++;
    }
}
dTime   = GetTime() - dTime;

double dmTime   = GetTime();
{
    volatile double i = 4;
    const double div = 1.0f / 4.0f;
    __int32 count   = 0;
    __int32 max     = 1000000;
    while( count < max )
    {
        i *= div;
        count++;
    }
}
dmTime  = GetTime() - dmTime;


DebugOutput( _T( "%f\n" ), i32Time );
DebugOutput( _T( "%f\n" ), i64Time );
DebugOutput( _T( "%f\n" ), fTime );
DebugOutput( _T( "%f\n" ), fmTime );
DebugOutput( _T( "%f\n" ), dTime );
DebugOutput( _T( "%f\n" ), dmTime );

DebugBreak();

然后我以32位模式在AMD64 Turion 64上運行它。 我得到的結果如下:

0.006622
0.054654
0.006283
0.006353
0.006203
0.006161

測試有缺陷的原因是使用volatile會強制編譯器從內存中重新加載變量,以防萬一它被更改。 所有這些都表明這台機器上的任何實現之間沒有什么區別(__int64顯然很慢)。

它還明確地表明MSVC編譯器通過倒數優化執行乘法運算。 我想GCC會做同樣的事情,如果不是更好的話。 如果我改變浮點數和雙除法檢驗除以“i”那么它會顯着增加時間。 雖然,雖然很多可能是從磁盤重新加載,但很明顯編譯器無法輕易地優化它。

要了解這種微觀優化,請嘗試閱讀此pdf。

我所有人都認為,如果你擔心這些事情,你顯然還沒有描述你的代碼。 在實際出現問題時,對問題進行概述和修復。

Agner Fog自己做了一些非常詳細的測量,可以在這里找到。 如果您真的想要優化內容,您還應該從他的軟件優化資源中閱讀其余文檔。

我要指出的是,即使您正在測量非向量化浮點運算,編譯器也會為生成的匯編提供兩個選項:它可以使用FPU指令( faddfmul ),也可以使用SSE指令同時仍然操作一個浮點運算每條指令的點值( addssmulss )。 根據我的經驗,SSE指令更快,並且具有更少的不准確性,但編譯器不會將其作為默認值,因為它可能會破壞與依賴於舊行為的代碼的兼容性。 您可以使用-mfpmath=sse標志在gcc中打開它。

暫無
暫無

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

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