Почему подпрограмма с массивом в качестве входных данных обеспечивает более высокую производительность, чем та же подпрограмма с автоматическим локальным массивом?

#fortran #gfortran

#fortran #gfortran

Вопрос:

Я переписываю некоторый устаревший код, чтобы улучшить читаемость и, надеюсь, упростить его обслуживание.

Я пытаюсь уменьшить количество входных параметров для подпрограмм, но обнаружил, что изменение subroutine sub(N, ID) —> subroutine sub(N) заметно снизило производительность.

ID используется только в sub , поэтому я не считаю, что имеет смысл использовать ее в качестве входных данных. Можно ли использовать sub(N) без снижения производительности? (Для моего использования N < 10, где производительность в 5-10 раз хуже.)

Сравнение производительности:

  1. sub_1

    • N = 4 , 0,9 секунды
    • N = 20 , 1,0 секунды
    • N = 200 , 2,1 секунды
  2. sub_2

    • N = 4 , 0,07 секунды
    • N = 20 , 0,18 секунды
    • N = 200 , 1,3 секунды

Я использую Mac OS 10.14.6 с gfortran 5.2.0

 program test
  integer, parameter  :: N = 1
  real, dimension(N)  :: ID


  call CPU_time(t1)

  do i = 1, 10000000
    CALL sub_1(N)
  end do

  call CPU_time(t2)
  write ( *, * ) 'Elapsed real time =', t2 - t1



  call CPU_time(t1)

  do i = 1, 10000000
    CALL sub_2(N, ID)
  end do

  call CPU_time(t2)
  write ( *, * ) 'Elapsed real time =', t2 - t1

end program test



SUBROUTINE sub_1(N)
  integer,            intent(in)      :: N
  real, dimension(N)                  :: ID

  ID = 0.0

END SUBROUTINE sub_1



SUBROUTINE sub_2(N, ID)
  integer,            intent(in)      :: N
  real, dimension(N), intent(in out)  :: ID

  ID = 0.0

END SUBROUTINE sub_2
  

Комментарии:

1. Не могли бы вы рассказать нам, как именно вы ее скомпилировали, пожалуйста?

2. Также было бы полезно иметь время в quest8n, а не в коде, я их почти пропустил

3. @IanBush Возможно, это наивно, но я просто использовал команду gfortran test.f95 , затем выполненную с ./a.out помощью (в терминале)

4. Спасибо! Пожалуйста, добавьте флаг -O3 в строку компиляции и повторите.

5. Вы также можете явно добавить -fstack-arrays опцию.

Ответ №1:

Похоже, это «особенность» старой версии gfortran, которую вы используете. Если я использую более поздние версии, по крайней мере, для N = 10, время намного более сопоставимо:

 ian@eris:~/work/stack$ head s.f90
program test
  integer, parameter  :: N = 10
  real, dimension(N)  :: ID


  call CPU_time(t1)

  do i = 1, 10000000
    CALL sub_1(N)
  end do
ian@eris:~/work/stack$ gfortran-5 --version
GNU Fortran (Ubuntu 5.5.0-12ubuntu1) 5.5.0 20171010
Copyright (C) 2015 Free Software Foundation, Inc.

GNU Fortran comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GNU Fortran
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING

ian@eris:~/work/stack$ gfortran-5 -O3 s.f90
ian@eris:~/work/stack$ ./a.out
 Elapsed real time =  0.149489999    
 Elapsed real time =   1.99675560E-06
ian@eris:~/work/stack$ gfortran-6 --version
GNU Fortran (Ubuntu 6.5.0-2ubuntu1~18.04) 6.5.0 20181026
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ian@eris:~/work/stack$ gfortran-6 -O3 s.f90
ian@eris:~/work/stack$ ./a.out
 Elapsed real time =   7.00005330E-06
 Elapsed real time =   5.00003807E-06
ian@eris:~/work/stack$ gfortran-7 --version
GNU Fortran (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ian@eris:~/work/stack$ gfortran-7 -O3 s.f90
ian@eris:~/work/stack$ ./a.out
 Elapsed real time =   8.00006092E-06
 Elapsed real time =   6.00004569E-06
ian@eris:~/work/stack$ gfortran-8 --version
GNU Fortran (Ubuntu 8.3.0-6ubuntu1~18.04.1) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ian@eris:~/work/stack$ gfortran-8 -O3 s.f90
ian@eris:~/work/stack$ ./a.out
 Elapsed real time =   9.00030136E-06
 Elapsed real time =   6.00004569E-06
  

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

Комментарии:

1. Спасибо. У меня было ощущение, что это может быть проблема с версией. Я обновлю. Я ценю помощь.

2. @NickBrady пожалуйста, ознакомьтесь с дополнительными комментариями по оптимизации компилятора, которые я только что добавил…

3. Возможности оптимизатора — очень вероятная проблема. Некоторые мысли, не было бы лучше иметь подпрограммы в отдельном файле (чтобы у компилятора было меньше шансов встроиться), а также добавить выходной аргумент, который получает значение, находящееся в цикле, например, добавленное в программную переменную (и не забудьте записать его, иначе оптимизатор компилятора снова может быть умным …). К сожалению, я не могу проверить эти идеи, поскольку у меня есть только одна версия компилятора.

Ответ №2:

sub_1 и sub_2 на самом деле не сопоставимы. В sub_1 вы выделяете ID , инициализируете все элементы, а затем выбрасываете их, когда подпрограмма возвращается (потому что она локальна для подпрограммы).

Поскольку этот ID массив никогда не используется, компилятор может оптимизировать его создание и инициализацию. Это то, что делает gfortran, если вы компилируете с -O3 . Сгенерированный код для sub_1 не делает ничего, кроме возврата.

В sub_2 нем все равно нужно установить все элементы ID равными 0.0.

Комментарии:

1. С учетом этой теории имеет ли смысл, что sub_2 это более быстрая подпрограмма?

2. Без оптимизации sub_2 , вероятно, будет быстрее, потому что ей не нужно выделять массив. Я ожидал бы, что оптимизация sub_1 будет быстрее. Но важнее то, что они не сопоставимы, потому что они не выполняют одно и то же.

3. Компилятор может выдать любой результат, он никогда не печатается.

Ответ №3:

Я предполагаю, что это связано с распределением массива.

Сам процесс выделения памяти требует времени. Когда вы передаете массив без изменений в подпрограмму sub_2 , я думаю, что очень вероятно, что подпрограмме не нужно выделять память для массива. Это может предполагать, что массивы создаются в куче, а не в стеке, но я не уверен на 100%.

С другой стороны, для подпрограммы sub_1 ей необходимо каждый раз заново выделять пространство для массива.

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