我试图了解自动并行化如何加速我正在编写的程序的执行。我创建了一个更简单的例子:
#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:用户用户名2017学习并行并行main.cpp(39):info C5012:由于'500'原因,循环未并行化
c:用户用户名2017学习并行并行main.cpp(39):info C5012:由于'500'原因,循环未并行化
c:用户用户名2017学习并行并行main.cpp(38):info C5012:由于'1000'原因,循环未并行化
你能帮我找到并行化这个循环的正确方法吗?谢谢。
机器只能在一定程度上“猜测”一个意图(并且只要这种意图对于预先连线的转换策略不清楚,就可以放弃),所以不要指望大规模的任何明智的技巧。不同的方法可能。市场营销人员将击败他们所有的鼓声并吹嘘他们所有的口哨声以出售自动“思考” - 产品,但现实却不同。即便是最好的最好的承认,最好的性能来自指令级分析,有时甚至可以避免超标量流水线处理器编织技巧,以便获得最后几纳秒,在最后一级的并行化代码性能中丢失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重用(仅支付~ +1.5 [ns]
用于任何缓存重用)。
它取决于矩阵的全局大小,L3,L2和L1缓存大小以及缓存行长度/关联性,更不用说如果代码要在虚拟机上运行则会产生额外的性能偏差设备。
可以使用lstopo
(在没有智能lscpu
服务的情况下的hwloc
)描绘静态大小和近似NUMA拓扑。
在这里,您可以读取缓存容量,它可以保存矩阵单元,以便从智能重用(遵循for(){...}
索引的缓存行跨越)中获得任何潜在的加速。
调整for()
-loop步进可以获得最佳性能,最好靠近CPU硬件可用的ILP级别(使用CPU指令级并行的另一种并行度,允许共同执行的微指令链(参见。关于这些细节的英特尔CPU出版物并在目标平台上进行了最佳测试(交叉编译将无法在目标CPU架构上实现此类优化而无需性能基准测试,最好是在体内目标平台上)。
在StackOverflow上,详细信息超出了此媒体格式的有限范围,但如果对性能调优感兴趣,您会发现这两种来源和您自己的实验实践经验将决定您的进一步步骤。为了以某种方式感知功率,我们制作了一个大矩阵线性代数项目来完成几个[TB]
矩阵处理,从大约126小时到几分钟(不计算加载阶段,将矩阵数据输入RAM),通过非常谨慎的并行代码设计,所以确实值得设计“正确”。
为了获得更高的性能,还必须避免O / S驱逐昂贵的预取数据,因此需要更多的努力来实现最终性能,而不仅仅是依靠自动“自动并行化”。
结语: 如果仍然存在疑问,如果确实有可能的话,为什么HPC中心仍然会照顾和培养HPC专家来设计最终的高性能代码,如果“自动并行化” - 它会做得更好或者至少与这些专家讨厌的极客?他们不会,如果他们确实可以的话。