为什么我无法通过缓存友好来加速我的程序?
Why did I failed to speed up my program by making it cache-friendly?
我使用了intel的ifort来编译我的程序
有很多像下面这样的嵌套循环
do i=1,imax
do j=1,jmax
if (a(i,j).ne.1) cycle
.....
.....
.....
enddo
enddo
以及像
这样的一些循环
do j=1,jmax
do i=1,imax
if (a(i,j).ne.1) cycle
.....
.....
.....
enddo
enddo
我注意到 Fortran 按列优先顺序存储数组,所以我将 j 循环放在 i 循环之外。
让我感到困惑的是,性能似乎有所下降。
相比之下,我试图将第 i 个循环放在第 j 个循环的外部,用于其他循环。但是仍然存在性能损失,但是轻微
program p
integer ::i, j, k, s
real ::a(1000,1000)
do i=1,1000
do j=1,1000
call random_number(a(i,j))
enddo
enddo
do k=1,10000
do j=1,1000
do i=1,1000
if (a(i,j) .ge. 111) s = s+a(i,j)
enddo
enddo
enddo
print *,s
end
ifort -fPIC -no-prec-div -O3 -xCORE-AVX2 a.f90 -o a
时间./a
10921820
real 0m2.221s
user 0m2.221s
sys 0m0.001s
program p
integer ::i, j, k, s
real ::a(1000,1000)
do i=1,1000
do j=1,1000
call random_number(a(i,j))
enddo
enddo
do k=1,10000
do i=1,1000
do j=1,1000
if (a(i,j) .ge. 111) s = s+a(i,j)
enddo
enddo
enddo
print *,s
end
ifort -fPIC -no-prec-div -O3 -xCORE-AVX2 a.f90 -o a
time ./a
10923324
real 0m4.459s
user 0m4.457s
sys 0m0.003s
性能差异稳定,可以证明优化。但是当我把它应用到我的实际项目中时,我失败了。
再次感谢您,我对堆栈溢出还很陌生,感谢您的帮助
正如您正确提到的那样,Fortran 是专栏专业的。这意味着该列是主要索引,并且矩阵的所有列都一个接一个地存储在内存中。您可能假设,列优先意味着遍历列索引意味着遍历内存中连续的元素的矩阵,但事实并非如此。矩阵
| a11, a12, a13 |
| a21, a22, a23 |
| a31, a32, a33 |
按列优先顺序存储为
a11, a21, a31, a12, a22, a32, a13, a23, a33
--column 1--- --column 2--- --column 3---
这意味着当你在 Fortran 中创建一个二维循环时,第一个索引,代表行索引,应该总是在最里面以获得最佳性能,因为这样你就可以按内存顺序遍历矩阵我在上面写的。因此
do j=1,1000
do i=1,1000
if (a(i,j) .ge. 111) s = s + a(i,j)
enddo
enddo
如您所见,性能最佳。
我使用了intel的ifort来编译我的程序
有很多像下面这样的嵌套循环
do i=1,imax
do j=1,jmax
if (a(i,j).ne.1) cycle
.....
.....
.....
enddo
enddo
以及像
这样的一些循环do j=1,jmax
do i=1,imax
if (a(i,j).ne.1) cycle
.....
.....
.....
enddo
enddo
我注意到 Fortran 按列优先顺序存储数组,所以我将 j 循环放在 i 循环之外。
让我感到困惑的是,性能似乎有所下降。
相比之下,我试图将第 i 个循环放在第 j 个循环的外部,用于其他循环。但是仍然存在性能损失,但是轻微
program p
integer ::i, j, k, s
real ::a(1000,1000)
do i=1,1000
do j=1,1000
call random_number(a(i,j))
enddo
enddo
do k=1,10000
do j=1,1000
do i=1,1000
if (a(i,j) .ge. 111) s = s+a(i,j)
enddo
enddo
enddo
print *,s
end
ifort -fPIC -no-prec-div -O3 -xCORE-AVX2 a.f90 -o a 时间./a 10921820
real 0m2.221s
user 0m2.221s
sys 0m0.001s
program p
integer ::i, j, k, s
real ::a(1000,1000)
do i=1,1000
do j=1,1000
call random_number(a(i,j))
enddo
enddo
do k=1,10000
do i=1,1000
do j=1,1000
if (a(i,j) .ge. 111) s = s+a(i,j)
enddo
enddo
enddo
print *,s
end
ifort -fPIC -no-prec-div -O3 -xCORE-AVX2 a.f90 -o a
time ./a
10923324
real 0m4.459s
user 0m4.457s
sys 0m0.003s
性能差异稳定,可以证明优化。但是当我把它应用到我的实际项目中时,我失败了。
再次感谢您,我对堆栈溢出还很陌生,感谢您的帮助
正如您正确提到的那样,Fortran 是专栏专业的。这意味着该列是主要索引,并且矩阵的所有列都一个接一个地存储在内存中。您可能假设,列优先意味着遍历列索引意味着遍历内存中连续的元素的矩阵,但事实并非如此。矩阵
| a11, a12, a13 |
| a21, a22, a23 |
| a31, a32, a33 |
按列优先顺序存储为
a11, a21, a31, a12, a22, a32, a13, a23, a33
--column 1--- --column 2--- --column 3---
这意味着当你在 Fortran 中创建一个二维循环时,第一个索引,代表行索引,应该总是在最里面以获得最佳性能,因为这样你就可以按内存顺序遍历矩阵我在上面写的。因此
do j=1,1000
do i=1,1000
if (a(i,j) .ge. 111) s = s + a(i,j)
enddo
enddo
如您所见,性能最佳。