перегрузка оператора fortran: функция или подпрограмма

Недавно я обновил свой код .f90 до .f03 и ожидал увидеть ускорение, потому что моя старая версия включала в себя множество операций выделения и освобождения (7 трехмерных массивов - 45x45x45) на каждой итерации внутри цикла do (всего 4000). Используя производные типы, я выделяю эти массивы в начале моделирования и освобождаю их в конце. Я думал, что увижу ускорение, но на самом деле он работает значительно медленнее (30 минут вместо 23 минут).

Я запустил профилировщик, и похоже, что операторы сложения / вычитания / умножения / деления занимают относительно много времени. Насколько я могу судить, кроме изменения в стандарте, единственное отличие - это операторы. Мне интересно, связано ли это с тем, что функции возвращают новые копии количеств полей во время каждой операции.

Итак, вот мой вопрос: может ли он работать быстрее, если я изменю функции на подпрограммы, чтобы эти поля передавались по ссылке (я думаю?)? Кроме того, если это быстрее и предпочтительнее, то почему во всех этих примерах показаны функции для перегрузки операторов вместо использования подпрограмм? Я чувствую, что что-то упускаю.

Ссылки на функции с перегрузкой операторов:

http://www.mathcs.emory.edu/~cheung/Courses/561/Syllabus/6-Fortran/operators.html

http://research.physics.illinois.edu/ElectronicStructure/498-s97/comp_info/overload.html

https://web.stanford.edu/class/me200c/tutorial_90/13_extra.html

person Charles    schedule 30.01.2015    source источник
comment
Да, размещаемые массивы   -  person Charles    schedule 30.01.2015


Ответы (1)


Здесь есть два вопроса:

  1. Могу ли я повысить производительность в некоторых ситуациях, используя подход подпрограмм вместо функционального подхода?
  2. Почему, если производительность хуже, мне нужно использовать функцию?

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

Однако я быстро придумал кое-что, что может помочь вам.

module test

  implicit none

  type t1
     real, allocatable :: x(:)
  end type t1

contains

  function div_fun(f,g) result(q)
    type(t1), intent(in) :: f, g
    type(t1) q
    q%x = f%x/g%x
  end function div_fun

  subroutine div_sub1(f, g, q)
    type(t1), intent(in) :: f, g
    type(t1), intent(out) :: q
    q%x = f%x/g%x
  end subroutine div_sub1

  subroutine div_sub2(f, g, q)
    type(t1), intent(in) :: f, g
    type(t1), intent(inout) :: q
    q%x(:) = f%x/g%x
  end subroutine div_sub2

end module test

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

Однако важно отметить, что происходит.

Для функции результат требует выделения, а для подпрограммы div_sub1 аргумент intent(out) требует выделения. [Назначение результата функции добавляет вещей - см. Позже.]

В div_sub2 распределение используется повторно (аргумент «результат» равен intent(inout)), и мы подавляем автоматическое перераспределение с помощью q%x(:). Эта последняя часть важна: компиляторы часто несут накладные расходы при проверке необходимости изменения размера. Эту последнюю часть можно проверить, изменив намерение q в div_sub1 на inout.

[Обратите внимание, что для этого div_sub2 подхода предполагается, что размеры не меняются; кажется, это подтверждается вашим текстом.]

В заключение по первому вопросу: проверьте сами, но задайтесь вопросом, просто ли вы «скрываете» выделения, используя производные типы, а не удаляя их. И вы можете получить очень разные ответы, используя параметризованные производные типы.

Переходя ко второму вопросу, почему функции обычно используются? Вы заметите, что я рассмотрел очень конкретные случаи:

q = div_fun(f,g)
call div_sub2(f,g,q)  ! Could be much faster

Из текста вопроса и ссылок (и предыдущих вопросов, которые вы задавали) я предполагаю, что у вас есть что-то, что перегружает оператор /

interface operator (/)
  module procedure div_fun
end interface

позволяя

q = f/g               ! Could be slower, but looks good.
call div_sub2(f,g,q)

поскольку мы отмечаем, что для использования в качестве бинарного оператора (см. Fortran 2008 7.1.5, 7.1.6) процедура должна быть функцией. В ответ на ваш комментарий к предыдущей редакции этого ответа

разве двоичные операторы div_sub1 и div_sub2 не похожи на div_fun?

ответ - «нет», по крайней мере, с точки зрения того, что Фортран определяет как бинарные операторы (см. ссылку выше). [Кроме того, div_fun сам по себе не является бинарным оператором, это комбинация функции и универсального интерфейса, образующего операцию.]

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

q = q + alpha*(f/g)                ! Very neat
call div_sub2(f,g,temp1)
call mult_sub(alpha, temp1, temp2)
call add_sub(q, temp2, temp3)
call assign_sub(q, temp3)

Использование подпрограмм может немного запутаться. Приведенный выше пример можно немного привести в порядок, обработав аспекты «на месте» (или специальные подпрограммы), но это подводит меня к заключительному пункту. Поскольку результат функции полностью оценивается перед его последующим использованием (включая присвоение), у нас есть такие ситуации, как

f = f/g  ! or f=div_fun(f,g)
call div_sub2(f,g,f) ! Beware aliasing

В заключение по второму вопросу: производительность - это еще не все.

[Наконец, если вы имеете в виду, что используете суффиксы файлов .f90 и .f03 для обозначения / регулирования соответствия стандартам, то вы можете узнать, что люди думают об этом.]

person francescalus    schedule 30.01.2015
comment
Спасибо, я попробую протестировать эти разные сценарии, чтобы увидеть, как меняется производительность. Любые комментарии о том, почему так много источников, похоже, используют функциональный подход? Спасибо еще раз! - person Charles; 31.01.2015
comment
Я должен был быть более конкретным в моем последнем комментарии. Я должен был сказать какие-нибудь комментарии о том, почему так много источников, похоже, используют функциональный подход вместо подпрограмм? Если ваш ответ все еще применим, то я запутался, разве двоичные операторы div_sub1 и div_sub2 не похожи на div_fun? - person Charles; 31.01.2015
comment
@Charlie Чего вам здесь не хватает, так это того, что вы фактически выполняете две операции, а не одну - вы делите f на g, а также присваиваете результат в q. div_fun и div_sub1 делают то же самое, но как вы их перегрузите? Если вы перегружаете / с помощью div_sub1, тогда вы просите компилятор сделать f/g, используя процедуру, которая принимает три аргумента, но есть только два, f и g. На самом деле вы просите q = div_sub(f,g), тогда div_sub должна быть функцией, чтобы это было законным. - person PetrH; 01.02.2015
comment
@francescalus, я сделал репо на github. Вот ссылка: github.com/charliekawczynski/FortranAllocateSpeed ​​ - person Charles; 03.02.2015