Im folgenden Code übergeben wir zwei Arrays an eine Subroutine und führen einige zusätzliche Operationen in DO-Schleifen durch. Hier betrachten wir drei Fälle, in denen verschiedene Operationen durchgeführt werden: Fall 1 = keine Operation, Fälle 2 und 3 = Zuweisung von Zeigervariablen.Leistungsabfall von DO-Schleifen durch mögliche Änderung gebundener Variablen
!------------------------------------------------------------------------
module mymod
implicit none
integer, pointer :: n_mod
integer :: nloop
contains
!.........................................................
subroutine test_2dim (a, b, n)
integer :: n
real :: a(n,n), b(n,n)
integer, pointer :: n_ptr
integer i1, i2, iloop
n_ptr => n_mod
do iloop = 1, nloop
do i2 = 1, n
do i1 = 1, n
b(i1,i2) = a(i1,i2) + b(i1,i2) + iloop
!(nothing here) !! Case 1 : gfort => 3.6 sec, ifort => 3.2 sec
! n_ptr = n !! Case 2 : gfort => 15.9 sec, ifort => 6.2 sec
! n_ptr = n_mod !! Case 3 : gfort => 3.6 sec, ifort => 3.5 sec
enddo
enddo
enddo
endsubroutine
endmodule
!------------------------------------------------------------------------
program main
use mymod
implicit none
integer, target :: n
real, allocatable :: a(:,:), b(:,:)
nloop = 10000 ; n = 1000
allocate(a(n, n), b(n, n))
a = 0.0 ; b = 0.0
n_mod => n
call test_2dim (a, b, n)
print *, a(n,n), b(n,n) !! for check
end
Hier bemerken wir, daß dieser Zeiger zu den oberen Grenzen der DO-Schleife über die Modulgröße (n_mod) verbunden ist. Daher sollte das Ändern der Zeigervariablen innerhalb der Schleife das Verhalten der Schleifen beeinflussen. Aber bitte beachten Sie, dass wir die Grenzen in der Praxis nicht ändern (nur eine Kopie der Variablen). gfortran 4.8 und ifort 14.0 mit -O3 ergaben das oben angegebene Timing. Bemerkenswerterweise ist Fall 2 im Vergleich zu Fall 1 sehr langsam, obwohl die Nettoberechnung nicht viel anders aussieht. Ich vermutete, dass dies daran liegen könnte, dass der Compiler nicht feststellen kann, ob die obere Grenze der zweiten Schleife (für i1) durch Zeigerzuweisung geändert wird, und vermeidet so eine aggressive Optimierung. Um dies zu überprüfen, habe ich getestet, die folgende Routine anstelle von test_2dim():
subroutine test_1dim (a, b, n)
integer :: n
real :: a(n * n), b(n * n)
integer, pointer :: n_ptr
integer iloop, i
n_ptr => n_mod
do iloop = 1, nloop
do i = 1, n * n
b(i) = a(i) + b(i) + iloop
! (nothing here) !! Case 1 : gfort => 3.6 sec, ifort => 2.3 sec
! n_ptr = n !! Case 2 : gfort => 15.9 sec, ifort => 6.0 sec
! n_ptr = n_mod !! Case 3 : gfort => 3.6 sec, ifort => 6.1 sec
enddo
enddo
endsubroutine
Hier ist der einzige Unterschied zwischen test_1dim() und test_2dim() ist, dass die Arrays A und B zugegriffen werden von 1- oder 2- dim-Indizes (im Wesentlichen kein Unterschied in der Höhe der Berechnung). Überraschenderweise ergab Fall 2 auch ein langsames Ergebnis, obwohl es nur eine einzelne DO-Schleife gibt. Da Fortran-DO-Schleifen die obere Grenze der Schleife nach dem Eintrag [Ref] bestimmen, erwartete ich, dass test_1dim() nicht von der Zeigerzuweisung beeinflusst würde, obwohl dies nicht der Fall war. Gibt es eine vernünftige Erklärung für dieses Verhalten? (Ich hoffe, dass ich nicht einige großen Fehler mache, die zu dieser Zeitdifferenz führt.)
Meine Motivation für diese Frage: Ich habe mit abgeleiteten Typen weitgehend mehrdimensionale Schleifen zu spezifizieren, zum Beispiel
module Grid_mod
type Grid_t
integer :: N1, N2, N3
endtype
....
subroutine some_calc (vector, grid)
type(Grid_t) :: grid
....
do i3 = 1, grid % N3
do i2 = 1, grid % N2
do i1 = 1, grid % N1
(... various operations...)
enddo
enddo
enddo
Bis jetzt habe ich nicht viel Aufmerksamkeit darauf verwendet, ob Grid_t Objekte TARGET oder POINTER Attribut erhalten (vorausgesetzt, dass es im Wesentlichen keine Auswirkung auf die Leistung hat). Ich denke jedoch, dass dies zu einem Leistungsabfall führen kann, wenn ein Compiler nicht bestimmen kann, ob die oberen Grenzen innerhalb der Schleifen konstant sind (obwohl ich niemals die Grenzen in tatsächlichen Codes ändern werde). Daher wäre ich für jeden Rat dankbar, ob ich vorsichtiger sein sollte, TARGET- oder POINTER-Attribute für gebundene Variablen zu verwenden (einschließlich Komponenten des abgeleiteten Typs, wie sie durch das obige Rasterobjekt angegeben sind).
aktualisiert
Anregung durch @francescalus Nach habe ich versucht, "Vorsatz (in), den Wert" auf das Scheinargument "n" zu befestigen. Das Ergebnis ist wie folgt:
test_1dim():
Case 1: gfort => 3.6 s, ifort => 2.3 s
Case 2: gfort => 3.6 s, ifort => 3.1 s
Case 3: gfort => 3.6 s, ifort => 3.4 s
test_2dim():
Case 1: gfort => 3.7 s, ifort => 3.1 s
Case 2: gfort => 3.7 s, ifort => 3.1 s
Case 3: gfort => 3.7 s, ifort => 6.4 s
Während ifort etwas unregelmäßig Ergebnis ergibt (6,4 s) für Fall 3 in test_2dim(), alle anderen Ergebnisse zeigen im Wesentlichen die beste Leistung. Dies legt nahe, dass die Behandlung von Grenzen durch den Compiler die Leistung beeinflusst (nicht aufgrund der Kosten der Zeigerzuweisung).Da es wichtig ist, dem Compiler mitzuteilen, dass die Grenzen konstant sind, habe ich auch versucht, das Pseudo-Argument n (hier nicht mit "int (in), value") in eine lokale Variable n_ zu kopieren und als Schleifengrenzen zu verwenden:
integer :: n !! dummy argument
integer :: n_ !! a local variable
...
n_ = n
do i2 = 1, n_
do i1 = 1, n_
b(i1,i2) = a(i1,i2) + b(i1,i2) + iloop
...
das Ergebnis für test_2dim() ist wie folgt:
test_2dim():
Case 1: gfort => 3.6 s, ifort => 3.1 s
Case 2: gfort => 15.9 s, ifort => 6.2 s
Case 3: gfort => 3.7 s, ifort => 6.4 s
hier leider (und meine Erwartung im Gegensatz), Fall 2 besserte sich nicht ... Obwohl das Kopieren auf eine lokale n_ sollte Damit garantiert wird, dass n_ in den DO-Schleifen konstant ist, scheint der Compiler nicht glücklich zu sein, da die Array-Form immer noch durch n bestimmt ist, nicht durch n_, wodurch eine aggressive Optimierung vermieden wird ()- nur meine Vermutung).
Update2
Nach dem Vorschlag von @innoSPG ich auch n geändert innen DO-Schleifen für Case 2, N_ und es stellt sich dann heraus, dass der Code so schnell wie Fall 1 läuft! Genauer gesagt, ist der Code
Aber wie die Antwort vorschlägt, kann diese Effizienz sein, weil die Zuordnung Anweisung vollständig vom Compiler eliminiert wird. Also denke ich, ich muss mehr praktische Codes (nicht zu einfach) prüfen, um die Auswirkungen von Zeigern oder Zeigerkomponenten auf die Schleifenoptimierung zu testen ...
(... Es tut mir leid für eine sehr lange Frage ...)
Ich denke auch, dass die Zeitdifferenz sein kann, weil die Zuordnung der Zeigervariable ziemlich teuer ist, aber nicht sehr sicher ... – roygvib
Versuchen Hinzufügen 'Absicht (in), value' auf die Erklärung von' n' in der Unterroutine (es sei denn, Sie wollen wirklich die Möglichkeit, Dinge zu ändern). – francescalus
@francescalus Ich habe die Frage aktualisiert, also bitte überprüfen Sie das Ergebnis. Und ich würde mich freuen, wenn Sie eine kurze Antwort (Erklärung) darüber (d. H., Warum das VALUE-Attribut die Leistung verbessert), also werde ich die Antwort akzeptieren. – roygvib