Fortran 2008: Как возвращаются возвращаемые значения функции?
Возможно ли в современном Фортране вернуть массив из функции с производительностью, эквивалентной подпрограмме, заполняющей массив, переданный в качестве аргумента?
Рассмотрим, например, как простой пример
PROGRAM PRETURN
INTEGER :: C(5)
C = FUNC()
WRITE(*,*) C
CALL SUB(C)
WRITE(*,*) C
CONTAINS
FUNCTION FUNC() RESULT(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END FUNCTION FUNC
SUBROUTINE SUB(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END SUBROUTINE SUB
END PROGRAM PRETURN
Здесь линия C = FUNC()
будет копировать значения из возвращаемого значения функции, перед тем как отбросить возвращенный массив из стека. Версия подпрограммы CALL SUB(C)
заполнит C
напрямую, избегая дополнительного шага копирования и использования памяти, связанного с временным массивом, - но делая использование в выражениях, таких как SUM(FUNC())
невозможно.
Однако, если реализация компилятора решила выделить все массивы в куче, возвращаемое значение может быть назначено просто путем изменения базового указателя C
, что приводит к эквивалентной производительности между двумя версиями.*
Проводятся ли такие оптимизации обычными компиляторами или есть какой-то другой способ получить семантику функций без снижения производительности?
* Это было бы более очевидно с распределенными массивами, но это столкнулось бы с проблемами поддержки компилятора. Intel fortran по умолчанию не перераспределяет массивы при назначении массива другого размера, но позволяет тот же эффект, используя ALLOCATE(C, SOURCE=FUNC())
заявление. Gfortran тем временем выполняет автоматическое распределение при назначении, но имеет ошибку, которая предотвращает ALLOCATE
заявления, где форма получается из SOURCE
аргумент и исправление еще не были включены в бинарные выпуски.
2 ответа
В стандарте Фортрана ничего не говорится о реальном механизме реализации чего-либо на языке. Семантика языка состоит в том, что результат функции полностью оценивается перед началом присваивания. Если кто-то передал пункт назначения в качестве вывода, то если функция по какой-то причине не завершилась, переменная может быть частично изменена. Компилятор может выполнить достаточный анализ перекрытий, чтобы несколько оптимизировать это. Я вполне уверен, что Intel Fortran не делает этого - семантические ограничения значительны.
Ваш пример - игрушечная программа - более интересный вопрос, есть ли производственные приложения, где такая оптимизация была бы применима и целесообразна.
Я прокомментирую, что Intel Fortran изменит свое поведение по умолчанию для назначений для выделяемых массивов, так что, начиная с версии 17, автоматическое перераспределение будет происходить, как указано в стандарте.
У меня иногда бывает то же самое. Когда я останавливаюсь и думаю об этом на мгновение, я понимаю, что функции хороши такими, какие они есть, и подпрограммы хороши такими же, как и в случае с Fortran.
Вообразите минуту, что возможность есть, и у нас есть следующая функция:
function doThings(param) results(thing)
integer :: thing
integer, intent(in out) :: param
! Local variables
integer :: genialUpdatedValue, onOfThePreviousResult
! some other declarations
! serious computation to do things
! and compute genialUpdatedValue and onOfThePreviousResult
param = genialUpdatedValue
thing = onOfThePreviousResult
end function doThings
И у нас есть следующие звонки:
! some variables first
integer, parameter :: N_THINGS = 50 ! just love 50
integer :: myThing, myParam
integer, dimension(N_THINGS) :: moreThings
!
! Reading initial param from somewhere
! myParam now has a value
!
myThing = doThings(myParam)
Это определенно хорошо, как насчет следующего
!
! Reading initial param from somewhere
! myParam now has a value
!
moreThing = doThings(myParam)
Что это даст в результате? Должно ли это быть
integer :: i
do i = 1, N_THINGS
moreThings(i) = doThings(myParam)
end do
или это будет этот
integer :: i, thing
thing = doThings(myParam)
do i = 1, N_THINGS
moreThings(i) = thing
end do
Помни что myParam
изменяется функцией. Можно утверждать, что это простой случай, но представьте, что результатом был не целое число, а пользовательский тип с большими элементами массива.
Если вы думаете об этом, вы обязательно найдете некоторые проблемы, подобные этим. Конечно, здесь и там можно добавить больше ограничений, чтобы разрешить эту функцию, и в конце концов, когда у нас будет достаточно спроса, она будет реализована с необходимыми ограничениями. Надеюсь, это поможет.