Каква е най-добрата практика, когато функция на Fortran връща масив?

Да кажем, че искам да напиша функция, която приема като вход масив x от едно измерение и връща друг масив y от същото измерение въз основа на него (за да го илюстрирам използвайки функция, която го умножава по 2). Имам две опции за този код:

function times2(x) result(y)
    real, intent(in) :: x(:)
    real, allocatable :: y(:)

    allocate(y(size(x))
    y = 2*x
end function

or

function times2(x,n) result(y)
    real, intent(in) :: x(n)
    integer, intent(in) :: n
    real  :: y(n)

    y = 2*x
end function

Лично аз предпочитам първия, защото е по-лесен за използване от повикващия, но не съм сигурен кой е по-добър по отношение на паметта, да предположим, че масивът x може да бъде огромен, не знам дали е по-добре да бъде отложен масив или автоматичен масив. Във всеки случай, кой е добрият начин да го направите в съвременния Fortran?


person Manuel Pena    schedule 22.02.2019    source източник
comment
Повечето хора, които познавам, биха използвали подпрограма, а не функция. Функциите, които променят своите аргументи, са просто зли - те причиняват объркване и разбирам, че не винаги е добре дефинирано точно какво правят, когато се използват в по-сложни изрази. Така че, ако трябва да използвате функция, използвайте първия начин, но мисля, че повечето хора биха казали, че подпрограмата е правилният начин.   -  person Ian Bush    schedule 22.02.2019
comment
Защо трябва да е по-лесно за обаждащия се?   -  person francescalus    schedule 22.02.2019
comment
Съгласен съм с коментара на @IanBush до известна степен. Въпросът е Защо да дефинираме функция за умножаване на масив по 2?, когато тази възможност е вградена в езика.   -  person High Performance Mark    schedule 22.02.2019
comment
умножаването на масив по две означава само да направи примерния код по-кратък. Действителният въпрос, който имам, е да кажем, че имам подпрограма, която взема масив с неизвестно измерение, прави куп неща и генерира друг масив със същото измерение, как да го направя?. (Използвам функция, а не подпрограма, защото целта на моята подпрограма е да генерира друг масив въз основа на първия, а не да променя първия)   -  person Manuel Pena    schedule 22.02.2019
comment
Пропуснато време за редактиране - в коментара по-горе, разбира се, частта за модифицирането на аргументи е без значение тук, разчетох кода погрешно.   -  person Ian Bush    schedule 22.02.2019
comment
Истинският въпрос, който имам е.... Обикновено е по-добре да зададете въпроса, на който искате да получите отговор.   -  person High Performance Mark    schedule 22.02.2019
comment
Под капака резултатите от функцията обикновено се реализират чрез създаване на подпрограма със скрит аргумент. Въпреки това може да се окажете с временен елемент в стека, ако използвате функцията в сложен израз. Но това може да се случи всеки път, когато използвате сложни изрази с масиви.   -  person Vladimir F    schedule 22.02.2019
comment
Не смятам, че трансформирането му в подпрограма е най-добрата практика @roygvib. Имам някои причини да го направя като функция. Функцията I може да бъде извикана на място. sqrt е функция, което означава, че можете да оценявате изрази като sqrt(1/(1 + sqrt(x)). Представете си, че sqrtе подпрограма...   -  person Manuel Pena    schedule 22.02.2019
comment
@VladimirF Разбирам, че искаш да кажеш, че по отношение на паметта няма голяма разлика, нали?   -  person Manuel Pena    schedule 22.02.2019
comment
@francescalus Мисля, че първият е по-лесен за повикващия, защото не трябва да се уверява, че x и n са последователни, повикващият предава x и всичко е последователно във функцията.   -  person Manuel Pena    schedule 22.02.2019
comment
Мисля, че бих използвал автоматичен масив за връщане за функции като cross(u, v) (за кръстосано произведение на два вектора, връщащи друг вектор), докато използвам разпределяем масив за връщане за функции като matmul(A, B), ако A или B може да бъде доста голям (и затова може да не се побира в стека).   -  person roygvib    schedule 22.02.2019
comment
значи ще напишеш cross(u,v,n) ? (съжалявам, ако съм те разбрал погрешно @roygvib)   -  person Manuel Pena    schedule 22.02.2019
comment
Но можете да пренапишете втория, за да имате y(size(x)) с x(:) все още (което, признавам, е това, което си мислех, че сте направили: вероятно съм прескочил действителния код въз основа на най-минималната разлика между автоматично и отложено.)   -  person francescalus    schedule 22.02.2019
comment
благодаря @francescalus, не знаех, че мога да използвам функция, когато декларирам формата на автоматичен масив   -  person Manuel Pena    schedule 22.02.2019
comment
@ManuelPena Вероятно бих се придържал към масиви с предполагаема форма (т.е. кръст (u, v), а не кръст (u, v, n)), защото човек може да прехвърли несъседни масиви с по-малки режийни разходи (срещу, може да възникне копиране за манекен с ясна форма). Но може да има случаи, в които явната форма е по-добра (не мога да си спомня такъв пример в момента, съжалявам...)   -  person roygvib    schedule 22.02.2019
comment
Мисля, че един изключителен (и важен) случай, когато фиктивните масиви с изрична форма са полезни, е когато някой иска да използва тази рутина от C (или други езици).   -  person roygvib    schedule 24.02.2019


Отговори (1)


Вероятно нито едното, нито другото, но както обикновено при тези неща, отговорът зависи от конкретни обстоятелства.

Ако приемем неелементарна операция, бих склонен да напиша такава функция (в модул) като:

function times2(x) result(y)
  real, intent(in) :: x(:)
  real :: y(size(x))

  y = 2*x
end function

Горното има фиктивен аргумент с предполагаема форма с автоматичен резултат от функцията. То:

  • е наличен при писане на стандарта Fortran 95;

  • указва изрично в източника зависимостта на размера на резултата от функцията от аргументите на функцията, което може (или не може) да помогне на читателите на вашия код да разберат какво се случва (един такъв четец е самият компилатор, който може да му помогне с оптимизацията );

  • може (или не) да избягва междинни копия на стойността на масива;

  • може (или не може) да изисква място за резултата от функцията или еквивалентен временен файл в стека.

Ако операцията беше елементарна (т.е. една и съща операция за всеки елемент, според действителния даден пример), бих написал елементарна функция. Източникът за такава функция приема скаларен аргумент и предоставя неразпределим скаларен резултат без указател.

elemental function times2(x) result(y)
  real, intent(in) :: x
  real :: y

  y = 2*x
end function

Обикновено използвам резултати от функция с разпределение на отложена форма, когато формата (или някакъв друг атрибут) на резултата от функцията не може да бъде описана с прост израз на спецификация. Резултати от функцията за разпределение:

  • изисква писане поне на стандарт Fortran 2003;

  • може да изисква допълнителна двойка разпределение/освобождаване на памет над това, което е строго необходимо, което може (или не може) да има последици за производителността;

  • може да не изисква същото използване на стека като автоматичния резултат, което може (или не) да избегне проблеми с препълване на стека по време на изпълнение.

Подробностите за изпълнението на компилатора (включително опциите на компилатора) влияят на сравнението. По-специално, разликите в начина, по който компилаторът управлява паметта за временни файлове, могат да накарат двата подхода да се сближат по отношение на техните изисквания за разпределение на стека и купчината.

Избягвайте изрични фиктивни аргументи на масив от форми, освен ако нямате специални изисквания.

person IanH    schedule 22.02.2019
comment
Благодаря ви, не знаех, че можете да посочите формата на изхода като size(x), но знаейки, че ще използвам и това. Говорех за неелементарен, все пак благодаря. - person Manuel Pena; 25.02.2019