使用 C++ 中的 OpenMP,矩阵乘法的性能保持不变
Performance of matrix multiplications remains unchanged with OpenMP in C++
auto t1 = chrono::steady_clock::now();
#pragma omp parallel
{
for(int i=0;i<n;i++)
{
#pragma omp for collapse(2)
for(int j=0;j<n;j++)
{
for(int k=0;k<n;k++)
{
C[i][j]+=A[i][k]*B[k][j];
}
}
}
}
auto t2 = chrono::steady_clock::now();
auto t = std::chrono::duration_cast<chrono::microseconds>( t2 - t1 ).count();
无论是否进行并行化,变量 t 都保持相当恒定。我不确定为什么会这样。也有一段时间 t 输出为 0。
我面临的另一个问题是,如果我将 n 的值增加到 500 之类的值,编译器将无法 运行 程序。(这里我取 n=100)
我正在使用 code::blocks 和 GNU GCC 编译器。
collapse
指令实际上可能对此负责,因为索引 j
是使用 divide/mod operations 重新创建的。
你试过没有collapse
吗?
建议的 OpenMP 并行化不正确,可能会导致错误的结果。当指定 collapse(2)
时,线程执行 "simultaneously" (j,k) 次迭代。如果两个(或多个)线程在相同的 j 但不同的 k 上工作,它们会将 A[i][k]*B[k][j]
的结果累积到相同的数组位置 C[i][j]
。这就是所谓的竞争条件,即 "two or more threads can access shared data and they try to change it at the same time" (What is a race condition?)。 尽管代码不是 OpenMP 有效的,但数据竞争不一定会导致错误的结果,并且会根据多种因素(调度、编译器实现、线程数等)产生错误的结果。为了解决上面代码中的问题,OpenMP 提供了 reduction
子句:
#pragma omp parallel
{
for(int i=0;i<n;i++) {
#pragma omp for collapse(2) reduction(+:C)
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
C[i][j]+=A[i][k]*B[k][j];
所以 "a private copy is created in each implicit task (...) and is initialized with the initializer value of the reduction-identifier. After the end of the region, the original list item is updated with the values of the private copies using the combiner associated with the reduction-identifier" (http://www.openmp.org/wp-content/uploads/openmp-4.5.pdf). Note that the reduction on arrays in C is directly supported by the standard since OpenMP 4.5 (check if the compiler support it, otherwise there are old manual ways to achieve it, Reducing on array in OpenMp).
然而,对于给定的代码,避免最内层循环的并行化可能更合适,这样根本不需要缩减:
#pragma omp parallel
{
#pragma omp for collapse(2)
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
C[i][j]+=A[i][k]*B[k][j];
对于小尺寸的矩阵 and/or 少量线程,Serial 可以比 OpenMP 版本更快。
在我使用多达 16 个内核、n=1000、GNU 编译器 v6.1 的英特尔机器上,当激活 -O3 优化时,盈亏平衡点约为 4 个内核,而使用 -O0 编译时,盈亏平衡点约为 2 个内核。为了清楚起见,我报告了我测量的性能:
Serial 418020
----------- WRONG ORIG -- +REDUCTION -- OUTER.COLLAPSE -- OUTER.NOCOLLAPSE -
OpenMP-1 1924950 2841993 1450686 1455989
OpenMP-2 988743 2446098 747333 745830
OpenMP-4 515266 3182262 396524 387671
OpenMP-8 280285 5510023 219506 211913
OpenMP-16 2227567 10807828 150277 123368
使用减少性能损失是巨大的(反向加速)。外层并行化(w or w/o collapse)是最好的选择。
关于大型矩阵的失败,可能的原因与可用堆栈的大小有关。尝试扩大系统和 OpenMP 堆栈大小,即
ulimit -s unlimited
export OMP_STACKSIZE=10000000
auto t1 = chrono::steady_clock::now();
#pragma omp parallel
{
for(int i=0;i<n;i++)
{
#pragma omp for collapse(2)
for(int j=0;j<n;j++)
{
for(int k=0;k<n;k++)
{
C[i][j]+=A[i][k]*B[k][j];
}
}
}
}
auto t2 = chrono::steady_clock::now();
auto t = std::chrono::duration_cast<chrono::microseconds>( t2 - t1 ).count();
无论是否进行并行化,变量 t 都保持相当恒定。我不确定为什么会这样。也有一段时间 t 输出为 0。 我面临的另一个问题是,如果我将 n 的值增加到 500 之类的值,编译器将无法 运行 程序。(这里我取 n=100) 我正在使用 code::blocks 和 GNU GCC 编译器。
collapse
指令实际上可能对此负责,因为索引 j
是使用 divide/mod operations 重新创建的。
你试过没有collapse
吗?
建议的 OpenMP 并行化不正确,可能会导致错误的结果。当指定 collapse(2)
时,线程执行 "simultaneously" (j,k) 次迭代。如果两个(或多个)线程在相同的 j 但不同的 k 上工作,它们会将 A[i][k]*B[k][j]
的结果累积到相同的数组位置 C[i][j]
。这就是所谓的竞争条件,即 "two or more threads can access shared data and they try to change it at the same time" (What is a race condition?)。 尽管代码不是 OpenMP 有效的,但数据竞争不一定会导致错误的结果,并且会根据多种因素(调度、编译器实现、线程数等)产生错误的结果。为了解决上面代码中的问题,OpenMP 提供了 reduction
子句:
#pragma omp parallel
{
for(int i=0;i<n;i++) {
#pragma omp for collapse(2) reduction(+:C)
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
C[i][j]+=A[i][k]*B[k][j];
所以 "a private copy is created in each implicit task (...) and is initialized with the initializer value of the reduction-identifier. After the end of the region, the original list item is updated with the values of the private copies using the combiner associated with the reduction-identifier" (http://www.openmp.org/wp-content/uploads/openmp-4.5.pdf). Note that the reduction on arrays in C is directly supported by the standard since OpenMP 4.5 (check if the compiler support it, otherwise there are old manual ways to achieve it, Reducing on array in OpenMp).
然而,对于给定的代码,避免最内层循环的并行化可能更合适,这样根本不需要缩减:
#pragma omp parallel
{
#pragma omp for collapse(2)
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
C[i][j]+=A[i][k]*B[k][j];
对于小尺寸的矩阵 and/or 少量线程,Serial 可以比 OpenMP 版本更快。 在我使用多达 16 个内核、n=1000、GNU 编译器 v6.1 的英特尔机器上,当激活 -O3 优化时,盈亏平衡点约为 4 个内核,而使用 -O0 编译时,盈亏平衡点约为 2 个内核。为了清楚起见,我报告了我测量的性能:
Serial 418020
----------- WRONG ORIG -- +REDUCTION -- OUTER.COLLAPSE -- OUTER.NOCOLLAPSE -
OpenMP-1 1924950 2841993 1450686 1455989
OpenMP-2 988743 2446098 747333 745830
OpenMP-4 515266 3182262 396524 387671
OpenMP-8 280285 5510023 219506 211913
OpenMP-16 2227567 10807828 150277 123368
使用减少性能损失是巨大的(反向加速)。外层并行化(w or w/o collapse)是最好的选择。
关于大型矩阵的失败,可能的原因与可用堆栈的大小有关。尝试扩大系统和 OpenMP 堆栈大小,即
ulimit -s unlimited
export OMP_STACKSIZE=10000000