Что вызывает разницу во времени выполнения в этом тривиальном коде фортрана? - PullRequest
2 голосов
/ 01 декабря 2011

Я наблюдал очень любопытный эффект в этой тривиальной программе

module Moo 
contains
   subroutine main()
      integer :: res 
      real :: start, finish
      integer :: i

      call cpu_time(start)

      do i = 1, 1000000000
         call Squared(5, res) 
      enddo
      call cpu_time(finish)

      print '("Time = ",f6.3," seconds.")',finish-start
   end subroutine

   subroutine Squared(v, res)
      integer, intent(in) :: v
      integer, intent(out) :: res 

      res = v*v 
   end subroutine 

!   subroutine main2()
!      integer :: res
!      real :: start, finish
!      integer :: i
!
!      call cpu_time(start)
!      
!      do i = 1, 1000000000
!         res = v*v
!      enddo
!      call cpu_time(finish)
!
!      print '("Time = ",f6.3," seconds.")',finish-start
!   end subroutine

end module
program foo 
   use Moo 
   call main()
!   call main2()
end program

Компилятор gfortran 4.6.2 на Mac.Если я компилирую с -O0 и запускаю программу, время составляет 4,36 секунды.Если я раскомментирую подпрограмму main2(), но не вызов, ее время в среднем составит 4,15 секунды.Если я также раскомментирую call main2(), то первое время станет 3,80, а второе - 1,86 (понятно, у меня нет вызова функции).

Я сравнил ассемблер, созданный во втором и третьем случаях (подпрограмма без комментариев; вызов прокомментировани не комментируются), и они абсолютно одинаковы, за исключением фактического вызова подпрограммы main2.

Как код может получить это повышение производительности от вызова к подпрограмме, которая произойдет в будущем, ив принципе нет разницы в результирующем коде?

Ответы [ 2 ]

6 голосов
/ 01 декабря 2011

Первое, что я заметил, было то, что ваша программа слишком коротка для правильного бенчмаркинга.Сколько трасс вы используете в среднем?Что такое стандартное отклонение?Я добавил вложенный цикл do в ваш код, чтобы сделать его длиннее:

do i = 1, 1000000000
  do j=1,10
    call Squared(5, res) 
  enddo
enddo

Я рассмотрел только случай 1 и случай 2 (закомментированный и не комментированный main2), потому что случай 3 отличается и не имеет значения для этого сравнения.Я ожидал бы небольшого увеличения времени выполнения в случае 2 из-за необходимости загружать больший исполняемый файл в память, даже если эта часть не используется в программе.

Так что я сделал синхронизацию (3 запуска каждый) дляслучаи 1 и 2 для трех компиляторов:

pgf90 10,6-0 64-разрядная цель на Linux x86-64 -tp istanbul-64

Intel (R) Fortran Intel (R) 64 CompilerXE для приложений, работающих на Intel (R) 64, версия 12.0.2.137, сборка 20110112

GNU Fortran (GCC) 4.1.2 20080704 (Red Hat 4.1.2-51)

на AMD Opteron(tm) Процессор 6134

Вывод моего сценария:

exp 1 with pgf90:
Time = 30.619 seconds.
Time = 30.620 seconds.
Time = 30.686 seconds.
exp 2 with pgf90:
Time = 30.606 seconds.
Time = 30.693 seconds.
Time = 30.635 seconds.
exp 1 with ifort:
Time = 77.412 seconds.
Time = 77.381 seconds.
Time = 77.395 seconds.
exp 2 with ifort:
Time = 77.834 seconds.
Time = 77.853 seconds.
Time = 77.825 seconds.
exp 1 with gfortran:
Time = 68.713 seconds.
Time = 68.659 seconds.
Time = 68.650 seconds.
exp 2 with gfortran:
Time = 71.923 seconds.
Time = 74.857 seconds.
Time = 72.126 seconds.

Обратите внимание, что разница во времени между случаем 1 и случаем 2 самая большая для gfortran и наименьшая для pgf90.

РЕДАКТИРОВАТЬ: После того, как Стефано Борини отметил, что я упустил из виду тот факт, что только цикл проверяется с помощью вызова cpu_time, время загрузки исполняемого файла выходит за рамки уравнения.Ответ А.Шелли предлагает возможную причину этого.При более длительном времени выполнения различие между двумя случаями становится минимальным.Тем не менее - я наблюдаю значительную разницу в случае gfortran (см. Выше)

5 голосов
/ 01 декабря 2011

Я думаю, что @ IRO-bot имеет правильный ответ, но я хотел бы отметить, что размещение кода может влиять на время даже для идентичной сборки.

У меня есть 2 встроенных приложения, работающих на идентичных процессорах. Каждый из них имеет одну и ту же процедуру сборки кода вручную, чтобы обеспечить максимально возможный цикл занятости (для вставки субмикросекундных задержек). Недавно я был удивлен, узнав, что в одном приложении цикл занял 50%! дольше, чем другой. Оба сгенерировали одинаковую сборку.

Оказывается, что в одном исполняемом файле начальный адрес тела цикла позволял ему полностью попадать в единственную строку кэша команд процессора. На более медленной, та же функция запускалась по адресу, который занимал две строки. Необходимая дополнительная выборка доминировала во времени такой тесной петли.

Таким образом, можно найти случаи, когда добавление неисполненного кода повлияет на синхронизацию кода из-за изменения последовательности кэширования инструкций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...