3

現代のFortranでは、配列に引数として渡されたサブルーチンを持つのと同等のパフォーマンスを持つ関数から配列を返すことは可能ですか?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())のような表現での使用は不可能です。

コンパイラの実装では、ヒープ上のすべての配列を割り当てることを選択したが、戻り値は2つのバージョン間の同等の性能が得られ、Cの根底にあるポインタを変更するだけで割り当てることができる場合。*

のようなものです一般的なコンパイラで行われた最適化、あるいはパフォーマンス上のオーバーヘッドなしに関数セマンティクスを得るための他の方法がありますか?


*割り当て可能な配列では明らかですが、これはコンパイラのサポートの問題になります。インテルFortranは、デフォルトでは、異なるサイズの配列の割り当て時には(再)配列を割り当てませんが、ALLOCATE(C, SOURCE=FUNC())ステートメントを使用すると同じ効果が得られます。一方、Gfortranは割り当て時に自動割り当てを行いますが、形状がSOURCE引数から派生し、修正がまだバイナリリリースに含まれていないALLOCATEステートメントを防ぐバグがあります。

答えて

7

Fortranの標準は、実際にはほとんど何も実装していないという点では静かです。言語のセマンティクスは、代入が始まる前に関数の結果が完全に評価されることです。出力先として出力先を渡した場合、何らかの理由で関数が完了しなかった場合、変数は部分的に変更される可能性があります。コンパイラはこれをいくらか最適化するのに十分なオーバーラップ解析を行うことができます。私は、インテルFortranがこれをしないと確信しています - 意味の制限が重要です。

あなたの例はおもちゃのプログラムです - このような最適化が適用可能で価値がある生産アプリケーションがある場合、より興味深い質問があります。

インテルFortranは、割り当て可能な配列への割り当てのデフォルト動作を変更し、バージョン17以降、標準で指定されているとおりに自動再割り当てが行われることをコメントします。

+0

万が一、fortranに「移譲」のセマンティクスを確立しようとしているのを知っていますか?あなたがデフォルトでそうすることに対して良い点を作ったのに対し、それは関数/戻り値に対する明示的な属性として実装できるもののようです。 – kdb

+0

私はこのことについて聞いたことはありませんでしたが、2008年以来のメンバーです。サブルーチンコールであなたが望むものを達成できるので、それは私にとっては不必要な複雑さのようです。 –

0

いつか同じことがあります。私はそれを一瞬止めて考えると、機能は良いと思っていて、サブルーチンは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 

を我々は次の呼び出しを持っている:

間違いなくOKです
! 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 

でなければならないか、それが、これは1

integer :: i, thing 
thing = doThings(myParam) 
do i = 1, N_THINGS 
    moreThings(i) = thing 
end do 

myParamは、関数によって変更されることを覚えておいてくださいしなければなりません。これは単純なケースだと主張できますが、結果は整数ではなく、大きな配列メンバーを持つユーザー定義の型であることをイメージします。

これについて考えると、間違いなくそのような問題が見つかるはずです。もちろん、その機能を可能にするためにここに追加することができ、最終的には十分な需要がある場合には、必要な制限が適用されます。私はそれが助けて欲しい

+0

ここで問題と考えていることを明確にしてください。コードは多少混乱しますが、問題はSAVE属性を持つ変数を返すことで、データへのポインタを返し、コピーによって値を返すということです。 – kdb

+0

ここには保存属性はありません。問題は戻り値が 'param'に基づいて計算され、' param'が常に変更されていることです。一度呼び出すと同じ値を使用して配列のすべてのセルを設定することは、配列の各セルの関数を呼び出すこととはまったく異なります。あなたが呼び出すたびに、関数内で 'param'が変更されたため戻り値が異なります。最も簡単な例は、関数のインクリメント 'param'と戻り値は' param'の二乗です。 – innoSPG

+0

本質的にあなたは配列*を返さないのですか?私はその部分を逃した。これは、答えが私の言う限りでは全く問題とは無関係になります。私の例は意図的に単純なおもちゃの例でしたが、割り振り可能なものを使用していないために単純すぎるかもしれませんが、アレイをあるメモリ領域から別のメモリ領域にコピーすることを回避することによって、 – kdb

関連する問題