Czy kompilatory Fortran naprawdę generują szybszy kod niż kompilatory C?


17

Kiedy studiowałem na uniwersytecie, często słyszałem pomysł, że kompilatory Fortran produkują szybszy kod niż kompilatory C dla równoważnego programu.

Kluczowe rozumowanie wyglądało następująco: kompilator Fortran emituje średnio 1,1 instrukcji procesora na wiersz kodu, podczas gdy kompilator C emituje średnio 1,6 instrukcji procesora na wiersz kodu - nie pamiętam dokładnych liczb, ale Pomysł polegał na tym, że kompilatory C emitowały zauważalnie więcej kodu maszynowego i dlatego produkowały wolniejsze programy.

Jak ważne jest takie porównanie? Czy możemy powiedzieć, że kompilatory Fortran produkują szybsze programy niż kompilatory C lub odwrotnie i dlaczego ta różnica istnieje?


19
Może to po prostu oznaczać, że programy Fortran są bardziej gadatliwe niż C ... Znaczące porównanie można wykonać tylko poprzez wdrożenie tej samej funkcjonalności w obu językach i porównanie wynikowego kodu maszynowego (wielkości i prędkości).
Péter Török

Czy wygenerowany kod obsługuje równoległe wykonywanie?

@ Péter Török, oznacza to po prostu, powiedzmy, że BLAS i LAPACK w Fortran działały znacznie lepiej niż jakikolwiek z portów C / C ++. Teraz różnica szybko się zmniejsza.
SK-logic

6
Możesz argumentować, że jeden kompilator produkuje szybszy kod, jeśli masz w 100% równoważny program w obu językach, napisany przez ekspertów znających ich kompilatory i mogących uwzględnić wydajność.
Falcon

Dawny Fortran nie obsługiwał rekurencji, a zatem niekoniecznie musiał wypychać argumenty wywołania funkcji na stos, ponieważ istniałaby statycznie przydzielona przestrzeń dla argumentów każdej funkcji. To jeden z powodów, dla których mogło być szybciej. Myślę, że możesz znaleźć pełniejszą odpowiedź tutaj: amazon.com/Programming-Language-Pragmatics-Third-Edition/dp/…
Pedro Rolo

Odpowiedzi:


36

IIRC jednym z głównych powodów, dla których mówi się, że Fortran jest szybszy, jest brak aliasingu wskaźnika , dzięki czemu mogą korzystać z optymalizacji, których nie mogą użyć kompilatory C:

W FORTRAN argumenty funkcji nie mogą się aliasować, a kompilator zakłada, że ​​nie. Umożliwia to doskonałą optymalizację i jest jednym z głównych powodów reputacji FORTRAN jako szybkiego języka. (Należy zauważyć, że aliasing może nadal występować w funkcji FORTRAN. Na przykład, jeśli A jest tablicą, a i i j są indeksami, które mają taką samą wartość, to A [i] i A [j] są dwiema różnymi nazwami ta sama lokalizacja pamięci. Na szczęście, ponieważ tablica podstawowa musi mieć tę samą nazwę, można przeprowadzić analizę indeksu, aby określić przypadki, w których A [i] i A [j] nie mogą być aliasami.)

Ale zgadzam się z innymi tutaj: porównywanie średniej liczby instrukcji asemblera wygenerowanych dla linii kodu jest kompletnym nonsensem. Na przykład nowoczesny rdzeń x86 może wykonywać dwie instrukcje równolegle, jeśli nie mają dostępu do tych samych rejestrów. Dzięki temu możesz (teoretycznie) zwiększyć wydajność o 100% dla tego samego zestawu instrukcji , zmieniając ich kolejność . Dobre kompilatory często generują również więcej instrukcji asemblowania, aby uzyskać szybszy kod (myśl rozwijanie pętli, wstawianie). Całkowita liczba instrukcji asemblera niewiele mówi o wydajności fragmentu kodu.


Innym powodem lepszych optymalizacji jest natywna obsługa liczb zespolonych.
SK-logic

Z pewnością poprawne dla Fortran IV lub mniej więcej. Nie jestem pewien, czy współczesne FORTRANY nadal nie mają wskaźników, dynamicznej pamięci itp.
Ingo

2
To jest ten sam powód, dla którego często rozwijaliśmy się w nieco wbudowanym asemblerze, pracując w C i C ++ w branży gier. Ludzie mogą twierdzić tak często, jak im się podoba, że ​​„kompilatory mogą optymalizować lepiej niż ludzie piszący asemblery”, w rzeczywistości aliasing wskaźników oznacza, że ​​często nie mogą . Kod, który możemy napisać ręcznie, byłby technicznie nielegalny dla kompilatora, wiedząc, że nie ma nic wspólnego z aliasingiem wskaźnika.
Carson63000,

5
Słowo restrictkluczowe C pozwala autorowi funkcji określić, że wskaźnik nie ma aliasów. Czy to wystarczy, aby zaradzić różnicy, czy może jest coś więcej?
bk.

@bk .: C „ogranicza” ataki „połowy problemu”; umożliwia stwierdzenie, że określony wskaźnik nie aliasuje niczego innego w ciągu swojego życia, ale nie ma sposobu, aby powiedzieć kompilatorowi, że obiekt, którego adres został przekazany do funkcji, nie zostanie przez nic aliasowany po powrocie tej funkcji.
supercat

8

Całkowicie nieprawidłowe porównanie.

Po pierwsze, jak zauważa @ Péter Török, musisz najpierw porównać liczbę linii w równoważnych programach z Fortran i C, aby było to nawet prawidłowe porównanie liczby wyprodukowanych linii.

Po drugie, mniej linii kodu nie zawsze oznacza szybsze programy . Nie wszystkie instrukcje maszyny wykonują tę samą liczbę cykli , ale występują również inne problemy, takie jak dostęp do pamięci , buforowanie itp.

Ponadto długie przebiegi kodu mogą być szybsze, ponieważ skutkuje to mniejszą liczbą wykonanych linii (tj. Liczba wierszy ! = Liczba wykonanych linii ).


5

Dan ma rację, dłuższe programy nie oznaczają wolniejszych programów. Bardzo zależy od tego, co robią.

Nie jestem ekspertem od Fortran, wiem trochę. Porównując je, uważam, że dobrze napisane C osiągnęłoby znacznie lepszą wydajność przy bardziej złożonych strukturach danych i funkcjonalności niż Fortran. Ktoś (proszę) popraw mnie, jeśli się tutaj mylę, ale myślę, że Fortran jest nieco na „niższym poziomie” niż C. Jeśli tak, jestem pewien, że pewne problemy pojawią się szybciej w Fortranie.

Kolejna rzecz, na pierwszy rzut oka myślałem, że pytasz, czy kompilatory są szybsze. Właściwie uważam, że Fortran generalnie kompiluje się szybciej dla podobnych ilości kodu, ale wynikowy program i sposób jego działania byłyby inną historią. Analizowanie jest prostsze.


2
Jeśli używasz złożonych struktur danych, FORTRAN jest prawdopodobnie złym wyborem. FORTRAN jest zoptymalizowany pod kątem bardzo szybkiego wykonywania prostych operacji kruszenia liczb.
Zachary K

4

Myślę, że po części jest to, że kompilatory FORTRAN są zaprojektowane do szybkiego wykonywania niektórych rodzajów matematyki. Dlatego ludzie korzystają z FORTRAN, aby wykonywać obliczenia tak szybko, jak to możliwe


4

To stwierdzenie mogło być prawdziwe w dawnych czasach (około późnych lat 70.), gdy C był w powijakach, a Fortran był wspierany przez wszystkich głównych producentów i był wysoce zoptymalizowany. Wczesne Fortrans były oparte na architekturze IBM, tak proste rzeczy jak arytmetyka, gdyby na pewno byłyby to jedna instrukcja na instrukcję montażu. Dotyczy to starszych maszyn, takich jak Data General i Prime, które miały 3 skoki. Nie działa to na nowoczesnych zestawach instrukcji, które nie mają skoku w 3 kierunkach.

Linie kodu nie są równe instrukcjom kodu. Wcześniejsze wersje Fortran dopuszczały tylko jedną instrukcję w wierszu. Późniejsze wersje Fortran mogą przyjmować wiele instrukcji w wierszu. C może mieć wiele instrukcji w wierszu. W szybszych kompilatorach produkcyjnych, takich jak Intel IVF (wcześniej CVF, MS Powerstation) i Intel C, naprawdę nie ma między nimi żadnej różnicy. Te kompilatory są wysoce zoptymalizowane.


4

FORTRAN w starym stylu wymagał, aby programista, który chciał udostępnić część tablicy dla funkcji, musiał przekazać odwołanie do całej tablicy, wraz z jedną lub większą liczbą liczb całkowitych określającą indeks dolny i indeks dolny końcowy lub liczbę elementów . C umożliwia uproszczenie tego do przekazywania wskaźnika na początek interesującej części wraz z liczbą elementów. Mówiąc wprost, przyspieszyłoby to sprawę (przekazując dwie rzeczy zamiast trzech). Pośrednio może to jednak spowolnić proces, ograniczając rodzaje optymalizacji, które może przeprowadzić kompilator.

Rozważ funkcję:

void diff(float dest[], float src1[], float src2[], int n)
{
  for (int i=0; i<n; i++)
    dest[i] = src1[i] - src2[i];
}

jeśli kompilator wiedziałby, że każdy ze wskaźników identyfikuje początek tablicy, mógłby wygenerować kod, który działałby na elementy tablicy równolegle lub w dowolnej kolejności, ponieważ dla dowolnego x! = y operacje na dest [x ] nie wpłynie na src1 [y] ani src2 [y]. Na przykład w niektórych systemach kompilator może skorzystać z generowania kodu odpowiadającego:

void dif(float dest[], float src1[], float src2[], int n)
{
  int i=0;
  float t1a,t1b,t2a,t2b,tsa,tsb;
  if (n > 2)
  {
    n-=4;
    t1a = src1[n+3]; t1b = src2[n+3]; t1b=src2[n+2]; t2b = src2[n+2];
    do
    {
      tsa = t1a-t2a;
      t1a = src1[n+1]; t2a = src2[n+1]; 
      tsb = t2b-t2b;
      dest[n+3] = tsa;
      t1b = src1[n]; t2b = src2[n]; 
      n-=2;
      dest[n+4] = tsb;
    } while(n >= 0);
    ... add some extra code to handle cleanup
  }
  else
    ... add some extra code to handle small values of n
}

Zauważ, że każda operacja, która ładuje lub oblicza wartość, ma co najmniej jedną dodatkową operację między nią a następną operacją, która korzysta z tej wartości. Niektóre procesory mogą nakładać się na przetwarzanie różnych operacji, gdy takie warunki są spełnione, co poprawia wydajność. Zauważ jednak, że ponieważ kompilator C nie ma możliwości dowiedzenia się, że kod nie zostanie przekazany wskaźnikom do częściowo pokrywających się regionów wspólnej tablicy, kompilator C nie może dokonać powyższej transformacji. Kompilatory FORTRAN, które otrzymały równoważny kod, mogły jednak dokonać takiej transformacji.

Podczas gdy programista C mógłby próbować osiągnąć porównywalną wydajność poprzez jawne wypisanie kodu, który rozwinął pętlę i nałożył się na operacje sąsiednich przejść, taki kod mógłby łatwo obniżyć wydajność, gdyby użył tak wielu zmiennych automatycznych, że kompilator musiałby je „przelać” do pamięć. Optymalizator kompilatora FORTRAN prawdopodobnie wiedziałby więcej niż programista o tym, jakie formy przeplotu zapewniłyby optymalną wydajność w danym scenariuszu, i takie decyzje często najlepiej pozostawić takim kompilatorom. Podczas gdy C99 próbował nieco poprawić sytuację C, dodając restrictkwalifikator, można go tutaj użyć tylko wtedy, gdy dest[]jest oddzielną tablicą od obu src1[]i src2[], lub jeśli programista dodał osobne wersje pętli, aby obsłużyć przypadki, w których wszystko destbyło rozłącznesrc1i src2gdziesrc1[]i destbyły równe i src2były rozłączne, gdzie src2[]i dest[]były równe i src1były rozłączne, i gdzie wszystkie trzy tablice były równe. FORTRAN, z drugiej strony, mógłby bez problemu obsługiwać wszystkie cztery przypadki przy użyciu tego samego kodu źródłowego i tego samego kodu maszynowego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.