使用VS

时间:2017-12-30 12:05:30

标签: c++ visual-studio parallel-processing

我试图理解自动并行化如何加速我正在编写的程序的执行。我创建了一个更简单的例子:

#include <iostream>
#include <vector>
#include <chrono>

using namespace std;
using namespace std::chrono;

class matrix
{
public:
   matrix(int size, double value) 
   {
       A.resize(size, vector<double>(size, value));
       B.resize(size, vector<double>(size, value));
   };
   void prodScal(double valore)
   {
        for (int m = 0; m < A.size(); m++)
            for (int n = 0; n < A.size(); n++)
            {
                B[m][n] = A[m][n] * valore;
            };
    };
    double elemento(int riga, int column) { return B[riga][column]; }

protected:
    vector<vector<double>> A, B;
};


void main()
{
    matrix* M;
    M = new matrix(1000, 174.9);
    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    #pragma loop(hint_parallel(4))
    for (int i = 0; i < 1000; i++)
        M->prodScal(567.3);

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>(t2 - t1).count();
    cout << "execution time [ms]: " << duration << endl;
 }

当我尝试使用cl main.cpp /O2 /Qpar /Qpar-report:2编译此代码时,收到以下消息:

  

c:\ users \ utente \ documents \ visual studio 2017 \ projects \ parallel \ parallel \ main.cpp(39):info C5012:ciclo non parallelizzato a causa del motivo'500'

     

c:\ users \ utente \ documents \ visual studio 2017 \ projects \ parallel \ parallel \ main.cpp(39):info C5012:ciclo non parallelizzato a causa del motivo'500'

     

c:\ users \ utente \ documents \ visual studio 2017 \ projects \ parallel \ parallel \ main.cpp(38):info C5012:ciclo non parallelizzato a causa del motivo'1000'

你能帮我正确地并行化这个循环吗? 感谢。

1 个答案:

答案 0 :(得分:0)

自动并行化努力(或信仰?)是一把相当双刃剑:

一台机器只能在一定程度上“猜测”一个意图(并且只要这种意图对预先连线的转换策略不清楚,就可以放弃),所以不要期望任何明亮的技巧。大规模的不同方法可能。市场营销人员将击败他们所有的鼓声并吹嘘他们所有的口哨声以出售自动“思考” - 产品,但现实却不同。即便是最好的最好的承认,最好的性能来自指令级分析,有时甚至可以避免超标量流水线处理器编织技巧,以便获得最后几纳秒,在最后一级的并行化代码性能中丢失CPU uop指令流程。所以,最好不要期望这样的专业知识仅仅通过在一个信念中使用#pragma代码部分来实现,“机器” - 将会发明一条最明智的方法。

所以,测试它(总是彻底)

尝试“并行化”最外层的for(){...}并不是开始时的最佳步骤。在性能方面和资源方面都是如此。让我们从不同的方面解决案例,计算本身:

#include <iostream>              // https://stackoverflow.com/questions/48033769/auto-parallelization-with-vs
#include <vector>
#include <chrono>                // g++ FLAGS.ADD: -std=c++11
#include <omp.h>                 // g++ FLAGS.ADD: -fopenmp -lm
#define   OMP_NUM_OF_THREADS 4

using namespace std;
using namespace std::chrono;

class matrix {
public:
   matrix( int size, double value ) {
           A.resize( size, vector<double>( size, value ) );
           B.resize( size, vector<double>( size, value ) );
   }
   void prodScal( double aScalarVALORE ) {
     // #pragma loop( hint_parallel(4) )                                           // matrix_(hint_parallel(4)).cpp:18:0: warning: ignoring #pragma loop  [-Wunknown-pragmas]
        #pragma omp parallel num_threads( OMP_NUM_OF_THREADS )                     // _____ YET, AGNOSTIC TO ANY BETTER CACHE-LINE RE-USE POLICY        
        for (     unsigned int m = 0; m < A.size(); m++ )
            for ( unsigned int n = 0; n < A.size(); n++ )
                  B[m][n] = A[m][n] * aScalarVALORE;
   }
   double elemento( int riga, int column ) { return B[riga][column]; }

protected:
    vector<vector<double>> A, B;
};

int main() {                              // matrix_(hint_parallel(4)).cpp:31:11: error: ‘::main’ must return ‘int’

    matrix* M;
    M = new matrix( 1000, 174.9 );
    high_resolution_clock::time_point t1 = high_resolution_clock::now();
 // *******************
 // DEFINITELY NOT HERE
 // *******************
 // #pragma loop(hint_parallel(4))       // JUST A TEST EXECUTION, NOT ANY PARALLELISATION BENEFIT FOR A PROCESS-PER-SE PERFORMANCE
    for ( int i = 0; i < 1000; i++ )
        M->prodScal( 567.3 );

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>( t2 - t1 ).count();
    cout << "execution time [ms]: " << duration << endl;
  /* 
   * execution time [ms]: 21601
     ------------------
    (program exited with code: 0)
   * */
    return 0;
 }

一旦有了工作代码,性能调整,获得最大值,是下一个障碍

更好地单步执行for(){...}可以显着提高所有MEM-fetches 的成本总和(为每个非缓存引用支付~ +100 [ns] v / s CACHE-re-use (只需~ +1.5 [ns]支付任何缓存重用)

它取决于矩阵的全局大小,L3,L2和L1高速缓存大小以及高速缓存行长度/关联性,更不用说如果要运行代码的额外性能偏差在虚拟设备上 Non-Uniform-Memory-Architecture topology with static sizes and hardware ID#-s
在没有智能lstopo服务的情况下,可以使用lscpuhwloc)来描述静态大小和近似NUMA拓扑。

在这里,您可以读取缓存容量,它可以保存矩阵单元,以便从智能重用(遵循for(){...}索引的缓存行跨越)中获得任何潜在的加速。

调整for() - 循环步进可以获得最佳性能,最好靠近CPU硬件可用的ILP级别(使用可能来自CPU指令级并行的另一个并行度,允许共同执行)微指令链(参考有关这些细节的英特尔CPU出版物)并在目标平台上进行了最佳测试(交叉编译将无法在目标CPU架构上实现此类优化而无需性能基准测试,最佳在体内目标平台上)

详细信息超出了此媒体格式的限制范围,在StackOverflow上,但绝对如果对性能调优感兴趣,您会发现这两种来源和您自己的实验实践经验将决定您的进一步步骤。为了以某种方式感知功率,我们制作了一个大矩阵线性代数项目来完成一些[TB]矩阵处理,从大约126小时到几分钟(不计算加载阶段,将矩阵数据输入RAM),非常谨慎的并行代码设计,所以确实值得设计“正确”。

为了获得更高的性能,还必须避免O / S驱逐昂贵的预取数据,因此需要更多努力才能获得最佳性能,而不仅仅是依靠自动化的“自动并行化”-toy

<子>的后记
如果仍然存在疑问,如果确实有可能的话,为什么HPC中心仍然会照顾和培养HPC专家来设计最终的高性能代码,如果“自动并行化” - 它会做得更好或者至少与这些专家讨厌的极客?如果确实可以,他们就不会。