2016-04-21 36 views
2

我正在試驗流體動力學代碼,在某些操作中降低浮點數的精度以測試是否真的需要雙精度。爲此,我寫了一個截斷函數,它將雙精度向量的精度降低到單精度,而不用轉換數據。這使我可以評估某些功能的準確性,而無需將代碼轉換爲單精度。由於這些評估的計算量很大,因此我的目標是儘可能提高性能的截斷功能。我嘗試了以下方法,有沒有什麼方法可以提高truncate函數的性能?在最高性能下將雙精度向量截斷爲單精度

#include <vector> 
#include <iostream> 
#include <iomanip> 
#include <chrono> 
#include <random> 

void truncate(std::vector<double>& v) 
{ 
    for (double& d : v) 
    { 
     float d_float = static_cast<float>(d); 
     d = static_cast<double>(d_float); 
    } 
} 

int main() 
{ 
    std::random_device rd; 
    std::mt19937 mt(rd()); 
    std::uniform_real_distribution<double> dist(0., 1.); 

    const int n = 512*512*512; 
    std::vector<double>v(n); 

    for (double& d : v) 
     d = dist(mt); 

    std::cout << "Before: " << std::setprecision(15) << v[0] << std::endl; 
    auto start = std::chrono::high_resolution_clock::now(); 
    truncate(v); 
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start); 
    std::cout << "After: " << std::setprecision(15) << v[0] << std::endl; 
    std::cout << "Duration in microseconds: " << duration.count() << std::endl; 
    return 0; 
}; 
+4

我不明白。你爲什麼認爲將double函數轉換爲float函數然後再轉換成double函數會提高計算性能? –

+0

我將編輯該問題。在考慮使代碼的精度變得很靈活的(大)痛苦之前,我想測試一下截斷精度的影響,以估計精度損失。爲此,我不得不重複一些昂貴的模擬,因此我想要一個快速的截斷函數。 – Chiel

+1

我想我看到你的困惑。數學運算在'float'上比在'double'上便宜。變量的*值*(除了一些我不會涉及的邊緣情況)對性能沒有影響,只有*類型*有。所以你想將* double的vector轉換成float的vector,然後在那個上運行你的計算來看看性能的差異。這需要改變類型。 –

回答

3

對於truncate函數的絕對性能,您可能需要手動執行操作;假設您可以訪問OpenMP和SSE內部函數,請查看_mm_cvtpd_ps(將2個雙精度轉換爲兩個浮點數)和_mm_cvtps_pd(轉換回雙精度)。

喜歡的東西:

double * vec; // aligned properly 
#pragma omp parallel for schedule(static, 512) 
for (int i = 0; i < size; i += 2) 
{ 
    _mm_store_pd(vec + i, _mm_cvtps_pd(_mm_cvtpd_ps(_mm_load_pd(vec + i)))); 
} 

這是什麼樣的事情,我會嘗試;你可以使用OpenMP選項,內部函數的確切形狀(也許使用AVX,如果你有可用的話)等等。

編輯:AVX變體只是_mm256_cvtpd_ps等等,如果你可以實現這個建議,你也可以實現AVX版本。

2

如果你只是想截斷雙打快速漂浮,有更快(和hackier)的方式來做到這一點。根據你已經知道或可以假設的數字,它可以更快或更慢。

  • 你可以有浮動比例非正常嗎?
  • 你有零嗎?
  • 你可以有NaN嗎?
  • 浮標上的數字可以是無窮大嗎?

對於這個解決方案,我假設你可以有零,但沒有反常規,NaN或無窮大。換句話說,我的浮標就不必每一個位屏蔽掉,並得到足夠接近的近似值:

for (double &d : doubles) { (*(uint64_t*)&d) &= 0xFFFF_FFFF_E000_0000; } 

這樣可以使你的符號位,指數,尾數和23位。爲了完全準確,您還需要修剪指數 - 但它會導致反常規(我們認爲不會發生)或無窮大(相同)。

請注意,通知處理器有關實際類型的解決方案可能會更好,更準確。這意味着作爲一個解釋性的帖子來說明一個浮動和一個double的實際區別。

+0

我不認爲「浮點數上的數字是無窮大」存在。如果值不能由目標類型表示,則從'double'到'float'的轉換具有未定義的行爲。 –

+1

@KerrekSB花車只能跑到2^127,但雙打可以跑到2^1023。在雙倍轉換爲無限大浮動的人之間有很大的規模。 C#至少做到這一點 - http://stackoverflow.com/questions/6640742/convert-double-to-float-without-infinity。 – dascandy

+2

這是一個關於C++的問題。 C++不會以這種方式進行轉換,相反,它會有不確定的行爲。 –

0

您是否考慮過使用多線程版本的截斷函數?例如:

void truncate(std::vector<double>& v, const int n_threads = 1) 
{ 
    if(n_threads <= 1) { 
    for (double& d : v) { 
     float d_float = static_cast<float>(d); 
     d = static_cast<double>(d_float); 
    } 
    } 
    else { 
    std::vector<std::thread> threads; 
    for (size_t id = 0; id < n_threads; ++id) { 
     auto threadFunc = [=,&v]() { 
     size_t beg = id*v.size()/n_threads; 
     size_t end = std::min(v.size(), (id+1)*v.size()/n_threads + (id == n_threads-1)*(v.size() % n_threads)); 
     for (size_t i=beg; i < end; ++i) { 
      float d = static_cast<float>(v[i]); 
      v[i] = static_cast<double>(d); 
     } 
     }; 
     threads.push_back(std::thread(threadFunc)); 
    } 
    for (auto & t : threads) t.join(); 
    } 
} 

對於大型載體,如果您可以使用許多線程負擔得起,增益應該很重要。

0

您是否考慮使用普通舊typedef(我更喜歡使用C++ 11的別名)作爲using myType = float,然後使用std::vector<myType>作爲想要在代碼中浮動的變量?這將精確地說明您的模擬的準確性和性能。

在這裏傳播使用myType需要一些時間,但它是值得的IMO,因爲你可以翻轉回來,如果你想。正如@steiner指出的那樣,儘可能多地使用並行結構也會提高性能。

+0

這不允許我獨立評估不同的運算符。此外,我寧願使用模板。 – Chiel

+0

@Chiel明白了。你會想遵循Stefan Atev的回答,然後使用內在函數。 – NameRakes