具有mkl后端的特征库的系数方阵运算的性能

时间:2015-08-19 10:30:40

标签: c++ performance intel-mkl eigen3

我正在将一个带有大量系数方面数组运算的Matlab算法移植到C ++中,这看起来像这个例子,但通常要复杂得多:

Eigen::Array<double, Dynamic, 1> tx2(12);
tx2 << 1,2,3,4,5,6;
Eigen::Array<double, Dynamic, 1> tx1(12);
tx1 << 7,8,9,10,11,12;
Eigen::Array<double, Dynamic, 1> x = (tx1 + tx2) / 2;

C ++代码比Matlab慢得多(约20%)。因此,在下一步中,我尝试打开Eigen的英特尔MKL实现,它对性能没有任何作用,就像字面上没有任何改进一样。 MKL是否有可能不改进系数方向矢量运算?有没有办法测试我是否成功链接了MKL?是否有更快的Eigen :: vector类替代品? 提前谢谢!

编辑:我在运行win7 64bit的i7-3820上使用VS 2013。 更长的例子是:

    Array<double, Dynamic, 1> ts = (k2 / (6 * b.pow(3)) + k / b - b / 2) - (k2 / (6 * a.pow(3)) + k / a - a / 2);
    Array<double, Dynamic, 1> tp1 = -2 * r2*(b - a)/ (rp.pow(2));
    Array<double, Dynamic, 1> tp2 = -2 * r2*rp*log(b / a) / rm2;
    Array<double, Dynamic, 1> tp3 = r2*(b.pow(-1) - a.pow (-1)) / 2;
    Array<double, Dynamic, 1> tp4 = 16 * r2.pow(2)*(r2.pow(2) + 1)*log((2 * rp*b - rm2) / (2 * rp*a - rm2)) / (rp.pow(3)*rm2);
    Array<double, Dynamic, 1> tp5 = 16 * r2.pow(3)*((2 * rp*b - rm2).pow(-1) - (2 * rp*a - rm2).pow(-1)) / rp.pow(3);
    Array<double, Dynamic, 1> tp = tp1 + tp2 + tp3 + tp4 + tp5;
    Array<double, Dynamic, 1> f = (ts + tp) / (2 * ds*ds);

CMakeLists的相关部分

    set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    target_link_libraries(MK ${VTK_LIBRARIES} ${Boost_LIBRARIES} mkl_intel_lp64_dll.lib mkl_intel_thread_dll.lib mkl_core_dll.lib libiomp5md.lib)

我到目前为止只定义了EIGEN_USE_MKL_ALL。

2 个答案:

答案 0 :(得分:4)

简而言之,如果您使用的是英特尔的C ++编译器,请使用它。

我构建了一个MCVE来测试这里做出的一些假设。我们想测试

  1. MKL的链接
  2. Eigen的矢量化
    1. 加成
    2. 乘法
    3. pow(double)
  3. 编制者的影响
  4. 使用Visual Studio 2013。

    #include <iostream>
    
    //#define EIGEN_DONT_VECTORIZE
    
    // SSE>2 doesn't affect these tests
    #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
    
    #define EIGEN_USE_MKL_ALL 
    
    #include <Eigen/Core>
    #include <ctime>
    #include <chrono>
    
    #include <mkl.h>
    
    int main(int argc, char* argv[])
    {
        srand(time(NULL));
        std::cout << Eigen::SimdInstructionSetsInUse() << "\n";
    
        int sz = 32 * 1024 * 1024;
    
        double dummyAdd, dummyMult, dummyPow;
    
        // Quick test to show linking worked
        {
            float a[16] = {23.54f};
            float r[16] = {0.f};
            float b = 2.f;
    
            vsPowx(4, a, b, r);
            std::cout << r[0] << "\n";
        }
    
        Eigen::ArrayXd v1 = Eigen::ArrayXd::Random(sz);
        Eigen::ArrayXd v2 = Eigen::ArrayXd::Random(sz);
        Eigen::ArrayXd v3 = Eigen::ArrayXd::Random(sz);
    
        auto startTime = std::chrono::high_resolution_clock::now();
        {
            v3 = v1 + v2;
            dummyAdd = v3.sum();
        }
        auto endTime = std::chrono::high_resolution_clock::now();
    
        std::cout << "Total Time (addition) " << dummyAdd << " = " <<
            std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << " milliseconds.\n";
    
        startTime = std::chrono::high_resolution_clock::now();
        {
            v1 = v3 * v2;   // 
            dummyMult = v1.sum();
        }
        endTime = std::chrono::high_resolution_clock::now();
    
        std::cout << "Total Time (multiplication) " << dummyMult << " = " <<
            std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << " milliseconds.\n";
    
        startTime = std::chrono::high_resolution_clock::now();
        {
            v3 = v1.pow(3.5);   // 
            dummyPow = v3.sum();
        }
        endTime = std::chrono::high_resolution_clock::now();
    
        std::cout << "Total Time (pow(3.5)) " << dummyPow << " = " <<
            std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << " milliseconds.\n";
    
        return 0;
    
    }
    

    然后我使用cl(VS的编译器)和英特尔C ++编译器XE 15.0编译,无论是否有EIGEN_DONT_VECTORIZE和EIGEN_USE_MKL_ALL。我为这些测试编译了没有 omp的。我得到了(i5 3470)有趣的结果。对于cl,我看到MKL是否被链接没有区别,而是轻微的

      


      554.132
      总时间(加法)-2006.37 = 130毫秒   总时间(乘法)1.11832e + 007 = 137毫秒   总时间(pow(3.5))-1。#IND = 1730毫秒。

      

    SSE,SSE2
      554.132
      总时间(加法)-689.959 = 86毫秒   总时间(乘法)1.1175e + 007 = 87毫秒   总时间(pow(3.5))-1。#IND = 1695毫秒。

    所以我们看到加法和乘法似乎是矢量化的,但pow不受MKL的影响。

    英特尔编译器在行为方面表现出类似的结果,但pow更好。

      


      554.132
      总时间(加法)7594.98 = 96毫秒   总时间(乘法)1.11818e + 007 = 94毫秒   总时间(pow(3.5))-1。#IND = 921毫秒。

      

    SSE,SSE2,SSE3,SSSE3,SSE4.1,SSE4.2
      554.132
      总时间(加法)-1953.37 = 87毫秒   总时间(乘法)1.11796e + 007 = 87毫秒   总时间(pow(3.5))-1。#IND = 838毫秒。

    没有EIGEN_USE_MKL_ALL和

      

    SSE,SSE2,SSE3,SSSE3,SSE4.1,SSE4.2
      554.132
      总时间(加法)1512.55 = 87毫秒   总时间(乘法)1.11759e + 007 = 89毫秒   总时间(pow(3.5))-1。#IND = 843毫秒。

    使用EIGEN_USE_MKL_ALL。

    我可以理解英特尔的编译器倾向于超级优化代码,以达到匹配MKL性能的程度。我希望看到cl性能有所不同。最重要的是,如果您需要更好的性能,请使用英特尔C ++编译器。

答案 1 :(得分:4)

将来自pow(2)pow(3)以及喜欢的来电替换为square()cube()pow(-1)相同,有利地由分部代替。我希望MatLab能够为您做所有这些优化,但在C ++中,只有在编译器级别工作才能使编译时优化成为可能。