2014-05-05 15 views
1

我只是做一些基準測試,發現通常比fabs()慢10倍。於是我拆開它,結果發現double版本使用的是fabs指令,float版本沒有。這可以改善嗎?這是更快,但沒有這麼多,我擔心它可能無法正常工作,這是一個有點過於低級:在C++中是否有「float」的快速fabsf替換?

float mabs(float i) 
{ 
    (*reinterpret_cast<MUINT32*>(&i)) &= 0x7fffffff; 
    return i; 
} 

編輯:對不起忘了編譯器 - 我還是用好老VS2005,沒有什麼特別的庫。

+9

您應該提及您使用的編譯器/庫,因爲這是一個庫實現細節。 –

+0

如果'fabs'真的快得多,也許你可以測試'(float)晶圓廠((double)floatval)',看看它是否比'fabsf'好。 – user2079303

+0

我其實嘗試了晶圓廠的基準測試((double)x),但它真的很慢。這真的很有趣:)。 –

回答

2

您是否試過std::abs過載float?那將是規範的C++方式。除此之外,我應該注意到,您的位修改版本確實違反了嚴格別名規則(除了更基本的假設,即intfloat具有相同的大小)並且因此將是未定義的行爲。

+0

[文檔爲'std :: abs'](http://en.cppreference.com/w/cpp/numeric/math/fabs) – Mgetz

+0

那麼,這可能會做C中的'fabsf'。哪個OP想要避免。 – delnan

+0

@delnan實際上它不會因爲C不接受基於參數的重載,所以默認情況下它不能是C函數。 – Mgetz

3

您可以使用the code below輕鬆測試不同的可能性。它從本質上檢驗了你對天真模板abs的煩惱,以及std::abs。毫不奇怪,天真模板腹肌獲勝。那麼,它有一個驚人的勝利。我期望std::abs同樣快。請注意,-O3實際上使事情變慢(至少在coliru上)。

Coliru的主機系統顯示這些時間:

random number generation: 4240 ms 
naive template abs: 190 ms 
ugly bitfiddling abs: 241 ms 
std::abs: 204 ms 
::fabsf: 202 ms 

而且這些時間的VirtualBox的VM上的酷睿i7運行拱與GCC 4.9:

random number generation: 1453 ms 
naive template abs: 73 ms 
ugly bitfiddling abs: 97 ms 
std::abs: 57 ms 
::fabsf: 80 ms 

而且在MSVS2013這些時間(Windows 7的x64):

random number generation: 671 ms 
naive template abs: 59 ms 
ugly bitfiddling abs: 129 ms 
std::abs: 109 ms 
::fabsf: 109 ms 

如果我沒有在這個基準上做出一些明顯的錯誤方舟代碼(不要讓我拍攝它,我在大約2分鐘內寫完了),我會說只是使用std::abs,或者模板版本,如果事實證明你的速度稍快。


代碼:

#include <algorithm> 
#include <cmath> 
#include <cstdint> 
#include <cstdlib> 
#include <chrono> 
#include <iostream> 
#include <random> 
#include <vector> 

#include <math.h> 

using Clock = std::chrono::high_resolution_clock; 
using milliseconds = std::chrono::milliseconds; 

template<typename T> 
T abs_template(T t) 
{ 
    return t>0 ? t : -t; 
} 

float abs_ugly(float f) 
{ 
    (*reinterpret_cast<std::uint32_t*>(&f)) &= 0x7fffffff; 
    return f; 
} 

int main() 
{ 
    std::random_device rd; 
    std::mt19937 mersenne(rd()); 
    std::uniform_real_distribution<> dist(-std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()); 

    std::vector<float> v(100000000); 

    Clock::time_point t0 = Clock::now(); 

    std::generate(std::begin(v), std::end(v), [&dist, &mersenne]() { return dist(mersenne); }); 

    Clock::time_point trand = Clock::now(); 

    volatile float temp; 
    for (float f : v) 
    temp = abs_template(f); 

    Clock::time_point ttemplate = Clock::now(); 

    for (float f : v) 
    temp = abs_ugly(f); 

    Clock::time_point tugly = Clock::now(); 

    for (float f : v) 
    temp = std::abs(f); 

    Clock::time_point tstd = Clock::now(); 

    for (float f : v) 
    temp = ::fabsf(f); 

    Clock::time_point tfabsf = Clock::now(); 

    milliseconds random_time = std::chrono::duration_cast<milliseconds>(trand - t0); 
    milliseconds template_time = std::chrono::duration_cast<milliseconds>(ttemplate - trand); 
    milliseconds ugly_time = std::chrono::duration_cast<milliseconds>(tugly - ttemplate); 
    milliseconds std_time = std::chrono::duration_cast<milliseconds>(tstd - tugly); 
    milliseconds c_time = std::chrono::duration_cast<milliseconds>(tfabsf - tstd); 
    std::cout << "random number generation: " << random_time.count() << " ms\n" 
    << "naive template abs: " << template_time.count() << " ms\n" 
    << "ugly bitfiddling abs: " << ugly_time.count() << " ms\n" 
    << "std::abs: " << std_time.count() << " ms\n" 
    << "::fabsf: " << c_time.count() << " ms\n"; 
} 

哦,並回答您的實際問題:如果編譯器不能生成更高效的代碼,我懷疑有一種更快的方式保存微優化裝配,特別是對於這樣的基本操作。

+0

我知道,如果你使用C風格的數組而不是矢量VS2013應該生成向量代碼,所以也應該測試一下(如果只是爲了看看SSE版本如何比較) – Mgetz

+0

其實我只是嘗試了樸素的代碼,它真的不是一場勝利。現在這是最慢的一次,最快的是晶圓廠重新加倍和返回。 –

+0

其實我只想測量一個單一的電話,沒有矢量,我有IPP。 –

2

在這裏玩很多事情。首先,x87協處理器不贊成使用SSE/AVX,所以我很驚訝地發現你的編譯器仍然使用fabs指令。在這個問題上發佈基準測試答案的其他人很可能使用支持SSE的平臺。 您的結果可能會大不相同。

我不知道爲什麼你的編譯器爲fabsfabsf使用不同的邏輯。完全可以將float加載到x87堆棧,並輕鬆地使用fabs指令。在沒有編譯器支持的情況下,自己再現這個問題的是,您不能將操作集成到編譯器的正常優化管道中:如果您說「加載此浮點數,使用fabs指令,將此浮點數返回到內存」,則編譯器會這樣做...並且它可能涉及將已經準備好處理的浮點數重新加載到內存中,使用fabs指令將其重新加載回去,並將其重新加載到內存中,然後再次將其加載到x87堆棧恢復正常的,可優化的管道。這將是四次浪費的加載存儲操作,因爲它只需要執行fabs

簡而言之,您不太可能擊敗浮點運算的集成編譯器支持。如果你沒有這種支持,內聯彙編器可能會讓事情變得比他們想象的更慢。你最快的做法可能是使用fabs函數代替浮點數的fabsf函數。

僅供參考,現代編譯器和現代的平臺上使用SSE指令andps(對於浮動)和andpd(雙打),以進出位標誌,非常喜歡你這樣做你自己,而是躲着所有語言的語義問題。他們都很快。現代編譯器也可能檢測到像x < 0 ? -x : x這樣的模式,並生成最優的指令,而不需要編譯器內在的。

+0

謝謝你詳盡的答案。仍然在VC2005這裏,但我認爲它是有道理的升級?我的意思是新編譯器的性能改進是什麼?當你假設一些使用你的軟件的計算機甚至不支持它時,用SSE生成代碼是否安全? –

+0

@VojtěchMeldaMeluzín,'andps'和'andpd'來自SSE2,它是在Pentium IV上引入的,所以我現在說它現在使用起來相當安全(仍然知道很多人使用P3s?)。就性能而言,過去9年來編譯器技術已經有了很大的改進,並且很可能在浮點處理之外的很多領域都有所改進。 – zneak

+0

@VojtěchMeldaMeluzín[Steam擁有很好的統計數據](http://store.steampowered.com/hwsurvey/)SSE,SSE2甚至SSE3在這一點上相當常見90%+ – Mgetz