我目前正在优化部分代码,因此会执行一些基准测试。
我有NxN
- 矩阵A
和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
的不同版本。如果这可能是原因,那么如果你能告诉我如何找到它就会很棒。
答案 0 :(得分:8)
理论上,您可以使用Fortran特定的OpenMP WORKSHARE
指令使Fortran数组操作并行:
!$OMP PARALLEL WORKSHARE
A(:,:) = T(:,:) * A(:,:)
!$OMP END PARALLEL WORKSHARE
请注意,虽然这是非常标准的OpenMP代码,但是一些编译器,尤其是英特尔Fortran编译器(ifort
),只需通过WORKSHARE
的方式实现SINGLE
构造。构造,因此没有任何平行的加速。另一方面,gfortran
将此代码片段转换为隐式PARALLEL DO
循环。请注意,gfortran
不会在工作共享结构中并行化标准数组表示法A = T * A
,除非它明确写为A(:,:) = 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个内核的多插槽系统上运行时,程序可以访问更多的最后一级缓存和更高的主内存带宽(假设每个CPU插槽中有一个单独的内存控制器的NUMA系统),因此性能提高,但只是因为有更多套接字而不是因为有更多核心。