I am working on issue #176282, which concerns differences in the numerical result of the SUM intrinsic between -O0 and -O1.
Current behavior
At present, Flang uses different implementations of SUM depending on the optimization level.
At -O0, SUM is lowered to a call to a Flang runtime function. For floating-point arrays, the runtime implementation uses Kahan summation.
At -O1 and higher, SUM is generally open-coded as a loop, using straightforward sequential accumulation rather than Kahan summation.
Consequently, changing the optimization level can change not only the generated code but also the summation algorithm itself.
I think it would be preferable for Flang to use a consistent algorithm across optimization levels, so I have been investigating which implementation should be used by default.
Experiment
I created an experimental change that makes the open-coded implementation used at -O1 and higher perform Kahan summation instead of straightforward accumulation.
The measurements were performed using Flang built from llvm-project commit 511286d5cffd, with this experimental change applied on top.
First, I measured the execution time of a small test program below whose runtime is dominated by calls to the SUM intrinsic. With the experimental implementation, the program was approximately four to five times slower than with straightforward accumulation.
program main
integer,parameter :: N=76
real(kind=4),parameter :: val=1._4
real(kind=4),dimension(1:N,1:N,1:N) :: a3
a3 = val
do i=1, N-1
do j=1, N
do k=1, N
a3(i+1,j,k) = a3(i,j,k) + val
enddo
enddo
enddo
write(1002) a3
rewind 1002
call sub
end program main
subroutine sub
integer,parameter :: N=76
real(kind=4),dimension(1:N,1:N,1:N) :: a
real(kind=4) :: result
integer :: c1, c2, rate
real(8) :: elapsed
call system_clock(count_rate=rate)
read(1002) a
call system_clock(c1)
do i = 1, 5000
result = sum(a)
enddo
call system_clock(c2)
elapsed = real(c2 - c1, kind=8) / real(rate, kind=8)
print *,' SUM: ',result
print *,' Time: ',elapsed
end subroutine sub
Next, I measured the performance impact on SPEC CPU 2017 using an NVIDIA Grace CPU. I selected only benchmarks that use the SUM intrinsic internally.
| Category | Benchmark | speedup |
|---|---|---|
| SPECrate INT | 548.exchange2_r | -0.022% |
| SPECrate FP | 521.wrf_r | -0.158% |
| 527.cam4_r | -0.437% | |
| SPECspeed INT | 648.exchange2_s | 0.088% |
| SPECspeed FP | 621.wrf_s | 0.500% |
| 627.cam4_s | -11.830% | |
| 628.pop2_s | 0.954% |
Most of the benchmarks showed little or no performance degradation. However, the performance of 627.cam4_s regressed by approximately 11.83%. Further analysis indicated that the main cause of the regression was the increased execution time of the SUM operations in the benchmark.
Comparison with Other Compilers
I also examined the behavior and generated assembly of other Fortran compilers, including gfortran and ifx. In the cases I tested, both compilers consistently used straightforward accumulation for the SUM intrinsic at every optimization level from -O0 through -O3.
Possible approach
Kahan summation generally provides better numerical accuracy than straightforward accumulation, so it is a reasonable algorithm for implementing SUM when accuracy is the primary concern.
However, an overhead of more than 10% in an application that uses SUM extensively may be unacceptable to many users. For this reason, I am hesitant to make Kahan summation the default implementation at all optimization levels.
One possible approach would be:
- Use straightforward accumulation as the default implementation of
SUM, consistently across optimization levels. - Provide a compiler option that requests a compensated summation algorithm, such as Kahan summation, for users who prefer improved numerical accuracy and are willing to accept the performance cost.
This would eliminate the current algorithmic difference between -O0 and optimized builds while still giving users a way to request a more accurate implementation.
Does this seem like a reasonable direction? I would appreciate feedback on both the choice of the default algorithm and the idea of providing a compiler option for compensated summation.