2017-09-25 55 views
2

我想用Eigen編寫一些上證所代碼,有些行爲沒有我。上證所表現特徵

鑑於代碼:

#ifndef EIGEN_DONT_VECTORIZE // Not needed with Intel C++ Compiler XE 15.0 
#define EIGEN_VECTORIZE_SSE4_2 
#define EIGEN_VECTORIZE_SSE4_1 
#define EIGEN_VECTORIZE_SSSE3 
#define EIGEN_VECTORIZE_SSE3 
#endif 

#include "stdafx.h" 
#include <iostream> 
#include <unsupported/Eigen/AlignedVector3> 
#include <Eigen/StdVector> 
#include <chrono> 

int _tmain(int argc, _TCHAR* argv[]) { 
    static const int SIZE = 4000000; 
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE(1, 1, 1); 
    //EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    //std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0,0,0)); 


    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE1(1, 1, 1); 
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE2(1, 1, 1); 
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE3(1, 1, 1); 
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE4(1, 1, 1); 

    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16); 

    A_SSE2 += B_SSE; 
    A_SSE3 = A_SSE2 + B_SSE; 
    A_SSE4 = A_SSE3 + B_SSE; 


    std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0, 0, 0)); 

    auto start2 = std::chrono::system_clock::now(); 

    // no unroll 
    for (int iteration = 0; iteration < SIZE; ++iteration) { 
     A_SSE += B_SSE; 
     C_SSE[iteration] = A_SSE; 
    } 

    //// own unroll 
    //for (int iteration = 0; iteration < SIZE/8; ++iteration){ 
    // A_SSE1 += B_SSE_increment_unroll; 
    // A_SSE2 += B_SSE_increment_unroll; 
    // A_SSE3 += B_SSE_increment_unroll; 
    // A_SSE4 += B_SSE_increment_unroll; 

    // C_SSE[iteration * 2] = A_SSE1; 
    // C_SSE[iteration * 2 + 1] = A_SSE2; 
    // C_SSE[iteration * 2 + 2] = A_SSE3; 
    // C_SSE[iteration * 2 + 3] = A_SSE4; 

    //} 

    auto end2 = std::chrono::system_clock::now(); 
    auto elapsed2 = end2 - start2; 
    std::cout << "Eigen aligned vector " << elapsed2.count() << '\n'; 

    Eigen::Matrix3Xf A = Eigen::Matrix3Xf::Zero(3, SIZE); 
    Eigen::Vector3f B(3, 3, 3); 
    Eigen::Vector3f C(2, 2, 2); 

    auto start1 = std::chrono::system_clock::now(); 

    for (int iteration = 0; iteration < SIZE; ++iteration) { 
     B += C; 
     A.col(iteration) = B; 
    } 
    auto end1 = std::chrono::system_clock::now(); 
    auto elapsed1 = end1 - start1; 
    std::cout << "Eigen matrix " << elapsed1.count() << '\n'; 


    float *pResult = (float*)_aligned_malloc(SIZE * sizeof(float) * 4, 16); // align to 16-byte for SSE 
    auto start3 = std::chrono::system_clock::now(); 

    __m128 x; 
    __m128 xDelta = _mm_set1_ps(2.0f);  // Set the xDelta to (4,4,4,4) 
    __m128 *pResultSSE = (__m128*) pResult; 

    x = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f); // Set the initial values of x to (4,3,2,1) 

    for (int iteration = 0; iteration < SIZE; ++iteration) 
    { 
     x = _mm_add_ps(x, xDelta); 
     pResultSSE[iteration] = x; 
    } 

    auto end3 = std::chrono::system_clock::now(); 
    auto elapsed3 = end3 - start3; 
    std::cout << "Own sse " << elapsed3.count() << '\n'; 

} 

時機似乎很奇怪,在我的電腦

  • 徵對準矢量展開:20057
  • 徵對齊矢量沒有UNROLL:〜120320
  • 特徵矩陣: 〜120207(與Align不展開相同)
  • 自己的SSE:160784

當我檢查程序集,對齊版本和Own SSE時,使用addps movaps,但是直到我手動展開循環,我沒有獲得額外的性能,即使我沒有在所有運行中執行(50%),沒有任何提升。版本機智Eigen Matrix不使用sse,實現相同的性能,內聯彙編顯示在16次迭代中展開。手動展開是否有影響力?我們是否應該爲SSE手動執行此操作,並且如果使用它的CPU屬性取決於它?

編輯: 所以總結一下。由於無法證明展開循環與未展開的展開循環相同,SSE指令執行效果不佳,因此無法隱藏存儲器存儲延遲。但是在彙編代碼中,「單個」指令只使用1個寄存器並在展開的循環中遞增。如果SSE上癮是垂直執行的(對齊向量中的單個浮點積累了相同的添加操作量),編譯器應該能夠證明展開的平等性。默認情況下,SSE操作是否未經編譯器優化?如果展開循環保持執行順序,那麼保留非關聯數學運算,自動展開應該是可能的,爲什麼它不會發生,以及如何強制編譯器執行它?

編輯: 作爲建議我跑的測試,但是從本徵替補單位不下的Visual Studio 2017年因此被

#include <iostream> 
#include <vector> 
#include <unsupported/Eigen/AlignedVector3> 
#include <chrono> 
#include <numeric> 

EIGEN_DONT_INLINE 
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out) 
{ 
    Eigen::AlignedVector3<float> A_SSE(1, 1, 1); 
    Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    for (auto &x : out) 
    { 
     A_SSE += B_SSE; 
     x = A_SSE; 
    } 
} 

EIGEN_DONT_INLINE 
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out) 
{ 
    Eigen::AlignedVector3<float> A_SSE1(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE2(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE3(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE4(1, 1, 1); 

    Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16); 

    A_SSE2 += B_SSE; 
    A_SSE3 = A_SSE2 + B_SSE; 
    A_SSE4 = A_SSE3 + B_SSE; 
    for (size_t i = 0; i<out.size(); i += 4) 
    { 
     A_SSE1 += B_SSE_increment_unroll; 
     A_SSE2 += B_SSE_increment_unroll; 
     A_SSE3 += B_SSE_increment_unroll; 
     A_SSE4 += B_SSE_increment_unroll; 
     out[i + 0] = A_SSE1; 
     out[i + 1] = A_SSE2; 
     out[i + 2] = A_SSE3; 
     out[i + 3] = A_SSE4; 
    } 
} 

EIGEN_DONT_INLINE 
void eigen_matrix(Eigen::Matrix3Xf& out) 
{ 
    Eigen::Vector3f B(1, 1, 1); 
    Eigen::Vector3f C(2, 2, 2); 

    for (int i = 0; i < out.cols(); ++i) { 
     B += C; 
     out.col(i) = B; 
    } 
} 

template<int unrolling> EIGEN_DONT_INLINE 
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out) 
{ 
    Eigen::Matrix<float, 3, unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1 + 2 * unrolling).template replicate<3, 1>(); 

    for (int i = 0; i < out.cols(); i += unrolling) { 
     out.middleCols<unrolling>(i) = B; 
     B.array() += float(2 * unrolling); 
    } 
} 

int main() { 
    static const int SIZE = 4000000; 

    int tries = 30; 
    int rep = 10; 


    std::vector<int> Timings(tries, 0); 
    { 
     Eigen::Matrix3Xf A(3, SIZE); 
#pragma loop(1) 
     for (int iter = 0; iter < tries; ++iter) 
     { 
      auto start1 = std::chrono::system_clock::now(); 
      eigen_matrix(A); 
      Timings[iter] = (std::chrono::system_clock::now() - start1).count(); 
     } 
    } 
    std::cout << "eigen matrix Min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n"; 
    std::cout << "eigen matrix Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0)/tries << " ms\n"; 

    { 
     Eigen::Matrix3Xf A(3, SIZE); 
#pragma loop(1) 
     for (int iter = 0; iter < tries; ++iter) 
     { 
      auto start1 = std::chrono::system_clock::now(); 
      eigen_matrix_unrolled<4>(A); 
      Timings[iter] = (std::chrono::system_clock::now() - start1).count(); 
     } 
    } 
    std::cout << "eigen matrix unrolled 4 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n"; 
    std::cout << "eigen matrix unrolled 4 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0)/tries << " ms\n"; 

    { 
     Eigen::Matrix3Xf A(3, SIZE); 
#pragma loop(1) 
     for (int iter = 0; iter < tries; ++iter) 
     { 
      auto start1 = std::chrono::system_clock::now(); 
      eigen_matrix_unrolled<8>(A); 
      Timings[iter] = (std::chrono::system_clock::now() - start1).count(); 
     } 
    } 
    std::cout << "eigen matrix unrolled 8 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n"; 
    std::cout << "eigen matrix unrolled 8 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0)/tries << " ms\n"; 

    { 
     std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0)); 
#pragma loop(1) 
     for (int iter = 0; iter < tries; ++iter) 
     { 
      auto start1 = std::chrono::system_clock::now(); 
      vector_no_unroll(A); 
      Timings[iter] = (std::chrono::system_clock::now() - start1).count(); 
     } 
    } 
    std::cout << "eigen vector min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n"; 
    std::cout << "eigen vector Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0)/tries << " ms\n"; 

    { 
     std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0)); 
#pragma loop(1) 
     for (int iter = 0; iter < tries; ++iter) 
     { 
      auto start1 = std::chrono::system_clock::now(); 
      vector_unrolled(A); 
      Timings[iter] = (std::chrono::system_clock::now() - start1).count(); 
     } 
    } 
    std::cout << "eigen vector unrolled min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n"; 
    std::cout << "eigen vector unrolled Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0)/tries << " ms\n"; 

} 

更換工作,並檢查結果在8個指出錯誤機(所有窗口)和得到如下結果

特徵矩陣民:110477毫秒

特徵矩陣平均值:131691毫秒

特徵矩陣展開4分鐘:40099毫秒

特徵矩陣展開4平均數:54812毫秒

特徵矩陣展開8分鐘:40001毫秒

特徵矩陣展開8平均數:51482毫秒

本徵向量分鐘:100270毫秒

特徵向量平均值:117316毫秒

特徵向量展開分鐘:59966毫秒

特徵向量展開平均值:65847毫秒

在每一個我測試機,exepted一個用是最老的。看起來像在新機器上小展開可能是非常有益的(結果在4倍展開時加速1.5到3.5倍,即使展開爲8,16,32或256時也不會增加)。

+0

*所有*優化應該是* per-CPU *。這一切都回到了根本問題:它太慢了嗎?如果答案是*是*,那麼我們就避免了過早的優化。你認爲這可能是一個不成熟的優化?假設是Intel SSE還是C++ SSE? – Sebivor

+0

它不是關於優化這些rutine,而是一般使用sse。通過每CPU的優化,你的意思是例如計數xmm寄存器和展開使用所有? – CzakCzan

+0

您可以展開以隱藏ADDPS或FMA延遲,以及避免循環開銷的前端瓶頸。例如對於一個點積:https://stackoverflow.com/questions/45113527/why-does-mulss-take-only-3-cycles-on-haswell-different-from-agners-instruction。 –

回答

1

你的計時是非常不準確的(當你多次運行你的代碼時,我會得到很多變化)。爲了獲得更好的重現性,您應該多次運行每個變體並花費最少的時間。我把一個基準使用它們是本徵的一部分BenchUtils:

#include <iostream> 
#include <unsupported/Eigen/AlignedVector3> 
#include <bench/BenchUtil.h> 

EIGEN_DONT_INLINE 
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out) 
{ 
    Eigen::AlignedVector3<float> A_SSE(1, 1, 1); 
    Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    for(auto &x : out) 
    { 
     A_SSE += B_SSE; 
     x = A_SSE; 
    } 
} 

EIGEN_DONT_INLINE 
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out) 
{ 
    Eigen::AlignedVector3<float> A_SSE1(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE2(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE3(1, 1, 1); 
    Eigen::AlignedVector3<float> A_SSE4(1, 1, 1); 

    Eigen::AlignedVector3<float> B_SSE(2, 2, 2); 
    Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16); 

    A_SSE2 += B_SSE; 
    A_SSE3 = A_SSE2 + B_SSE; 
    A_SSE4 = A_SSE3 + B_SSE; 
    for(size_t i=0; i<out.size(); i+=4) 
    { 
     A_SSE1 += B_SSE_increment_unroll; 
     A_SSE2 += B_SSE_increment_unroll; 
     A_SSE3 += B_SSE_increment_unroll; 
     A_SSE4 += B_SSE_increment_unroll; 
     out[i + 0] = A_SSE1; 
     out[i + 1] = A_SSE2; 
     out[i + 2] = A_SSE3; 
     out[i + 3] = A_SSE4; 
    } 
} 

EIGEN_DONT_INLINE 
void eigen_matrix(Eigen::Matrix3Xf& out) 
{ 
    Eigen::Vector3f B(1, 1, 1); 
    Eigen::Vector3f C(2, 2, 2); 

    for (int i = 0; i < out.cols(); ++i) { 
     B += C; 
     out.col(i) = B; 
    } 
} 

template<int unrolling> EIGEN_DONT_INLINE 
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out) 
{ 
    Eigen::Matrix<float,3,unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1+2*unrolling).template replicate<3,1>(); 

    for (int i = 0; i < out.cols(); i+=unrolling) { 
     out.middleCols<unrolling>(i) = B; 
     B.array() += float(2*unrolling); 
    } 
} 

int main() { 
    static const int SIZE = 4000000; 

    int tries = 10; 
    int rep = 10; 
    BenchTimer t; 

    std::cout.precision(4); 
    { 
     std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0)); 
     BENCH(t, tries, rep, vector_no_unroll(A)); 
     std::cout << "no unroll: " << 1e3*t.best(CPU_TIMER) << "ms\n"; 
    } 
    { 
     std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0)); 
     BENCH(t, tries, rep, vector_unrolled(A)); 
     std::cout << "unrolled:  " << 1e3*t.best(CPU_TIMER) << "ms\n"; 
    } 
    { 
     Eigen::Matrix3Xf A(3, SIZE); 
     BENCH(t, tries, rep, eigen_matrix(A)); 
     std::cout << "eigen matrix: " << 1e3*t.best(CPU_TIMER) << "ms\n"; 
    } 
    { 
     Eigen::Matrix3Xf A(3, SIZE); 
     BENCH(t, tries, rep, eigen_matrix_unrolled<4>(A)); 
     std::cout << "eigen unrd<4>: " << 1e3*t.best(CPU_TIMER) << "ms\n"; 
    } 
    { 
     Eigen::Matrix3Xf A(3, SIZE); 
     BENCH(t, tries, rep, eigen_matrix_unrolled<8>(A)); 
     std::cout << "eigen unrd<8>: " << 1e3*t.best(CPU_TIMER) << "ms\n"; 
    } 
} 

,我十分相似的時間幾乎獨立於-msse2-msse4.2-mavx2編譯:

no unroll: 66.72ms 
unrolled:  66.83ms 
eigen matrix: 57.56ms 
eigen unrd<4>: 50.39ms 
eigen unrd<8>: 51.19ms 

值得注意的是,AligenedVector3變體始終是最慢的,展開與否之間沒有顯着差異。矩陣變體需要大約7/8的時間,手動展開矩陣變體(每次迭代處理4或8列),將時間減少到原始時間的大約3/4。

這表明內存帶寬可能是所有向量化變體的瓶頸。展開的矩陣變體可能受實際操作(或單個標量的手動拷貝)限制。

基準測試是在Intel Core i5-4210U CPU @ 1.70GHz上使用Ubuntu 16.04上的g ++ 5.4.1進行的,最近簽出了Eigen開發分支。

+0

感謝您的完整答案,但有什麼辦法來檢查內存帶寬限制?有沒有什麼「經驗法則」來決定是否使用SSE,或者它對於機器來說是如此特定的,而不是從一開始就排除某些情況?誠實,即使我用sqrt替換簡單的加法,我沒有得到任何加速英特爾酷睿i7-7700HQ @ 2.8GHZ在Windows 10上。 – CzakCzan

+0

關於內存帶寬檢查,例如:https://stackoverflow.com/questions/3386042/how -to-measure-memory-bandwidth-utilization-on-windows如果你在循環之間沒有任何數據依賴,我認爲手動展開不會帶來任何顯着的好處。 – chtz

+0

但是當然,可能會有一些(舊的或者低級的)CPU在分支預測非常糟糕的情況下部分循環展開將會增加性能(當然這也會增加指令高速緩存的使用...) – chtz