4

我目前正在优化我的部分代码,因此执行一些基准测试。

我有NxN-matricesA并且T想要将它们逐元素相乘并A再次保存结果,即A = A*T. 由于此代码不可并行化,我将分配扩展为

!$OMP PARALLEL DO
do j = 1, N
    do i = 1, N
        A(i,j) = T(i,j) * A(i,j)
    end do
end do
!$OMP END PARALLEL DO

(完整的最小工作示例位于http://pastebin.com/RGpwp2KZ。)

现在发生的奇怪事情是,无论线程数(1 到 4 之间)如何,执行时间都或多或少保持不变(+- 10%),但是 CPU 时间会随着线程数的增加而增加。这让我认为所有线程都在做同样的工作(因为我在 OpenMP 方面犯了一个错误),因此需要相同的时间。

但是在另一台计算机(有 96 个 CPU 内核可用)上,程序的行为与预期一样:随着线程数的增加,执行时间会减少。令人惊讶的是,CPU 时间也减少了(最多约 10 个线程,然后再次上升)。

可能是安装了不同的OpenMP版本gfortran。如果这可能是原因,如果您能告诉我如何找出答案,那就太好了。

4

1 回答 1

9

理论上,您可以使用特定于 Fortran 的 OpenMPWORKSHARE指令使 Fortran 数组操作并行:

!$OMP PARALLEL WORKSHARE
A(:,:) = T(:,:) * A(:,:)
!$OMP END PARALLEL WORKSHARE

请注意,尽管这是相当标准的 OpenMP 代码,但某些编译器,尤其是英特尔 Fortran 编译器 ( ifort),WORKSHARE仅通过构造来实现SINGLE构造,因此不会提供任何并行加速。另一方面,gfortran将此代码片段转换为隐式PARALLEL DO循环。请注意,除非将其明确写gfortran为.A = T * AA(:,:) = T(:,:) * A(:,:)

现在关于性能和缺乏加速。A和矩阵的每一列都T占用(2 * 8) * 729 = 11664字节。一个矩阵占用 8.1 MiB,两个矩阵合计占用 16.2 MiB。这可能超过了 CPU 的最后一级缓存大小。此外,乘法代码的计算强度非常低 - 它每次迭代获取 32 字节的内存数据并在 6 次 FLOP 中执行一次复数乘法(4 次实数乘法,1 次加法和 1 次减法),然后将 16 字节存储回内存,这导致(6 FLOP)/(48 bytes) = 1/8 FLOP/byte. 如果内存被认为是全双工的,即它支持边读边写,那么强度会上升到(6 FLOP)/(32 bytes) = 3/16 FLOP/byte. 因此,问题在于内存受限,甚至单个 CPU 内核也可能使所有可用内存带宽饱和。例如,一个典型的 x86 内核每个周期可以退出两个浮点运算,如果以 2 GHz 运行,它可以提供 4 GFLOP/s 的标量数学运算。为了让这样的核心忙于运行你的乘法循环,主内存应该提供(4 GFLOP/s) * (16/3 byte/FLOP) = 21.3 GiB/s. 这个数量或多或少超过了当前一代 x86 CPU 的实际内存带宽。这仅适用于具有非矢量化代码的单核。添加更多内核和线程不会提高性能,因为内存根本无法以足够快的速度传递数据以保持内核忙碌。相反,性能会受到影响,因为拥有更多线程会增加更多开销。当在像 96 核这样的多插槽系统上运行时,程序可以访问更多的最后一级缓存和更高的主内存带宽(假设 NUMA 系统在每个 CPU 插槽中都有单独的内存控制器),因此性能会提高,但这只是因为有更多的插槽而不是因为有更多的内核

于 2013-07-24T11:28:32.847 回答