To pytanie jest rozszerzeniem dwóch dyskusji, które pojawiły się ostatnio w odpowiedziach na „ C ++ vs Fortran for HPC ”. I jest to trochę więcej wyzwanie niż pytanie ...
Jednym z najczęściej słyszanych argumentów na korzyść Fortrana jest to, że kompilatory są po prostu lepsze. Ponieważ większość kompilatorów C / Fortran ma ten sam zaplecze, kod wygenerowany dla semantycznie równoważnych programów w obu językach powinien być identyczny. Można jednak argumentować, że kompilacja C / Fortran jest mniej lub bardziej łatwa w optymalizacji.
Postanowiłem więc wypróbować prosty test: dostałem kopię daxpy.f i daxpy.c i skompilowałem je z gfortran / gcc.
Teraz daxpy.c jest tylko tłumaczeniem f2c daxpy.f (automatycznie generowany kod, brzydki jak cholera), więc wziąłem ten kod i trochę go wyczyściłem (poznaj daxpy_c), co w zasadzie oznaczało ponowne zapisanie wewnętrznej pętli jako
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Na koniec przepisałem go ponownie (wpisz daxpy_cvec), używając składni wektorowej gcc:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
Zauważ, że używam wektorów o długości 2 (to wszystko na co pozwala SSE2) i że przetwarzam jednocześnie dwa wektory. Jest tak, ponieważ w wielu architekturach możemy mieć więcej jednostek mnożenia niż elementów wektorowych.
Wszystkie kody zostały skompilowane przy użyciu gfortran / gcc w wersji 4.5 z flagami „-O3 -Wall-msse2 -march = native -ffast-matematyka -fomit-frame-pointer -malign-double -fstrict-aliasing”. Na moim laptopie (procesor Intel Core i5, M560, 2,67 GHz) otrzymałem następujące dane wyjściowe:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
Tak więc oryginalny kod Fortrana zajmuje nieco ponad 8,1 sekundy, jego automatyczne tłumaczenie zajmuje 10,5 sekundy, naiwna implementacja C robi to w wersji 7.9, a kod wyraźnie wektoryzowany robi to w wersji 5.6, nieznacznie mniej.
To Fortran jest nieco wolniejszy niż naiwna implementacja C i 50% wolniejszy niż wektoryzowana implementacja C.
Oto pytanie: jestem rodzimym programistą C i jestem przekonany, że wykonałem dobrą robotę w tym kodzie, ale kod Fortrana został ostatnio zmieniony w 1993 roku i dlatego może być nieco nieaktualny. Ponieważ nie czuję się tak komfortowo kodować w Fortranie, jak inni tutaj, czy ktoś może wykonać lepszą robotę, tj. Bardziej konkurencyjną w porównaniu do którejkolwiek z dwóch wersji C?
Ponadto, czy ktoś może wypróbować ten test za pomocą icc / ifort? Składnia wektorowa prawdopodobnie nie zadziała, ale byłbym ciekawy, jak zachowuje się naiwna wersja C. To samo dotyczy każdego, kto leży wokół XLC / XLF.
Mam przesłanych źródła i Makefile tutaj . Aby uzyskać dokładne czasy, ustaw CPU_TPS w teście. C na liczbę Hz na twoim CPU. Jeśli znajdziesz jakieś ulepszenia którejkolwiek z wersji, opublikuj je tutaj!
Aktualizacja:
Dodałem kod testowy stali do plików online i uzupełniłem go o wersję C. Zmodyfikowałem programy tak, aby tworzyły pętle 1'000'000 na wektorach o długości 10'000, aby były spójne z poprzednim testem (i ponieważ moja maszyna nie mogła przydzielić wektorów o długości 1'000'000'000, jak w oryginale stali kod). Ponieważ liczby są teraz nieco mniejsze, skorzystałem z opcji, -par-threshold:50
aby kompilator miał większe szanse na równoległość. Zastosowana wersja icc / ifort to 12.1.2 20111128, a wyniki są następujące
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
Podsumowując, wyniki są, dla wszystkich praktycznych celów, identyczne zarówno dla wersji C, jak i Fortran, a oba kody są równoległe automatycznie. Pamiętaj, że szybkie czasy w porównaniu z poprzednim testem wynikają z zastosowania arytmetyki zmiennoprzecinkowej o pojedynczej precyzji!
Aktualizacja:
Chociaż tak naprawdę nie podoba mi się to, dokąd zmierza ciężar dowodu, przekodowałem przykład mnożenia macierzy stali w C i dodałem go do plików w Internecie . Oto wyniki potrójnej pętli dla jednego i dwóch procesorów:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
Zauważ, że cpu_time
w Fortran mierzy czas procesora, a nie zegar ścienny, więc zawinąłem wywołania, time
aby porównać je dla 2 procesorów. Nie ma prawdziwej różnicy między wynikami, z wyjątkiem tego, że wersja C działa nieco lepiej na dwóch rdzeniach.
Teraz matmul
polecenie, oczywiście tylko w Fortranie, ponieważ ta właściwość nie jest dostępna w C:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
Łał. To absolutnie okropne. Czy ktoś może dowiedzieć się, co robię źle, lub wyjaśnić, dlaczego ta wewnętrzna właściwość jest w jakiś sposób dobra?
Nie dodałem dgemm
wywołań do testu porównawczego, ponieważ są to wywołania biblioteczne do tej samej funkcji w Intel MKL.
Dla przyszłych badań, może ktoś zasugerować przykład znany być wolniejszy niż w C w Fortran?
Aktualizacja
Aby zweryfikować twierdzenie stali, że matmul
istotna jest „rząd wielkości” szybciej niż wyraźny produkt macierzowy na mniejszych matrycach, zmodyfikowałem swój własny kod, aby pomnożyć macierze o rozmiarze 100x100, stosując obie metody, po 10 000 razy każda. Wyniki na jednym i dwóch procesorach są następujące:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
Aktualizacja
Grisu słusznie wskazuje, że bez optymalizacji gcc konwertuje operacje na liczbach zespolonych na wywołania funkcji biblioteki, podczas gdy gfortran opisuje je w kilku instrukcjach.
Kompilator C wygeneruje ten sam, zwarty kod, jeśli opcja -fcx-limited-range
jest ustawiona, tzn. Kompilator jest instruowany, aby ignorować potencjalne przepływy / niedomiar w wartościach pośrednich. Ta opcja jest jakoś domyślnie ustawiona w gfortran i może prowadzić do niepoprawnych wyników. Zmuszanie -fno-cx-limited-range
w gfortran nic nie zmieniło.
Jest to zatem argument przemawiający przeciwko używaniu gfortranu do obliczeń numerycznych: operacje na złożonych wartościach mogą przekraczać / spadać, nawet jeśli prawidłowe wyniki mieszczą się w zakresie zmiennoprzecinkowym. To w rzeczywistości standard Fortrana. W gcc lub ogólnie w C99 domyślnie robi się ściśle (czytaj zgodnie z IEEE-754), chyba że określono inaczej.
Przypomnienie: należy pamiętać, że głównym pytaniem było to, czy kompilatory Fortran produkują lepszy kod niż kompilatory C. To nie jest miejsce do dyskusji na temat ogólnych zalet jednego języka nad drugim. Chciałbym naprawdę zainteresować się tym, czy ktokolwiek znajdzie sposób na nakłonienie gfortranu do wyprodukowania daxpy tak wydajnego jak ten w C przy użyciu wyraźnej wektoryzacji, ponieważ ilustruje to problemy z poleganiem na kompilatorze wyłącznie w celu optymalizacji SIMD lub przypadek, w którym kompilator Fortran wyprzedza swój odpowiednik C.