Основной ответ здесь "это не так". Может быть, с конкретным компилятором и конкретными обстоятельствами, встроенная функция не так хорошо оптимизирована, как явный цикл DO, но так быть не должно. Я тестировал с помощью ifort 19, и даже на уровнях оптимизации по умолчанию внутренняя функция SPREAD и явный цикл генерировали похожий код, причем внутренняя функция была быстрее, когда я исправляю программу для использования результата.
Iteration 0.2187500 0.1376885
Spread 9.3750000E-02 0.1376885
Я бы также предупредил (как я это сделал в комментариях к вашему вопросу), что упрощенные эталонные программы часто не измеряют то, что, по мнению автора, они делают. Самая распространенная ошибка, которую демонстрируют оба ваших исходных и пересмотренных примера, заключается в том, что результат тестируемой работы никогда не используется, поэтому достаточно умный компилятор может просто испарить всю операцию. Действительно, когда я создаю оба ваших тестовых примера с помощью ifort 19, компилятор полностью удаляет всю работу, оставляя только временный код. Само собой разумеется, это бежит довольно быстро.
implicit none
integer, parameter :: n=500
integer :: k
real :: d2(n,n)
real :: d3(n,n,n)
contains
! Iteration
subroutine benchmark_a(res)
real, intent(out) :: res(n,n,n)
do k = 1, size(d3,3)
res(:,:,k) = d2*d3(:,:,k)
end do
end subroutine
! Spread
subroutine benchmark_b(res)
real, intent(out) :: res(n,n,n)
res = d3*spread(d2, 3, size(d3,3))
end subroutine
end module
program main
use benchmarks
real :: tstart,tend
real :: res(n,n,n)
call random_number(d2)
call random_number(d3)
! Iteration
call cpu_time(tstart)
call benchmark_a(res)
call cpu_time(tend)
write(*,*) 'Iteration', tend-tstart, res(10,10,10)
! Spread
call cpu_time(tstart)
call benchmark_b(res)
call cpu_time(tend)
write(*,*) 'Spread', tend-tstart, res(10,10,10)
end program```