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.
źródło
restrict
słowo kluczowe, które mówi kompilatorowi dokładnie to: aby założyć, że tablica nie pokrywa się z żadną inną strukturą danych.Odpowiedzi:
Różnica w taktowaniu wydaje się wynikać z ręcznego rozwijania daxpy z Fortranem . Poniższe czasy dotyczą Xeon X5650 2,67 GHz za pomocą polecenia
Kompilatory Intel 11.1
Fortran z ręcznym rozwijaniem: 8,7 s
Fortran bez ręcznego rozwijania: 5,8 s
C bez ręcznego rozwijania: 5,8 s
Kompilatory GNU 4.1.2
Fortran z ręcznym rozwijaniem: 8,3 s
Fortran bez ręcznego rozwijania: 13,5 s
C bez ręcznego rozwijania: 13,6 s
C z atrybutami wektorowymi: 5,8 s
Kompilatory GNU 4.4.5
Fortran z ręcznym rozwijaniem: 8,1 sek.
Fortran bez ręcznego rozwijania: 7,4 sek.
C bez ręcznego rozwijania: 8,5 sek.
C z atrybutami wektorowymi: 5,8 sek.
Wnioski
Czas przetestować bardziej skomplikowane procedury, takie jak dgemv i dgemm?
źródło
Spóźniam się na to przyjęcie, więc ciężko jest mi podążać w tę i z powrotem z góry. Pytanie jest duże i myślę, że jeśli jesteś zainteresowany, można je podzielić na mniejsze części. Jedno, co mnie zainteresowało, to po prostu wydajność twoich
daxpy
wariantów i to, czy Fortran jest wolniejszy niż C na tym bardzo prostym kodzie.Działając zarówno na moim laptopie (Macbook Pro, Intel Core i7, 2,66 GHz), względna wydajność Twojej wektoryzowanej wersji C i nie wektoryzowanej wersji Fortran zależy od zastosowanego kompilatora (z Twoimi własnymi opcjami):
Wydaje się więc, że GCC lepiej wektoryzowało pętlę w gałęzi 4.6 niż wcześniej.
Wydaje mi się, że w ogólnej debacie można napisać szybki i zoptymalizowany kod zarówno w języku C, jak i Fortran, prawie tak jak w asemblerze. Zwrócę jednak uwagę na jedno: tak jak asembler jest bardziej żmudny w pisaniu niż C, ale daje lepszą kontrolę nad tym, co jest wykonywane przez procesor, tak C jest bardziej niski niż Fortran. W ten sposób daje większą kontrolę nad szczegółami, co może pomóc w optymalizacji, gdzie standardowa składnia Fortran (lub jego rozszerzenia dostawcy) może nie mieć funkcjonalności. Jednym z przypadków jest jawne użycie typów wektorów, innym jest możliwość ręcznego określenia wyrównania zmiennych, czego Fortran nie jest w stanie.
źródło
Sposób, w jaki napisałbym AXPY w Fortranie, jest nieco inny. To jest dokładne tłumaczenie matematyki.
m_blas.f90
Teraz wywołajmy powyższą procedurę w programie.
test.f90
Teraz skompilujmy i uruchommy ...
Zauważ, że nie używam żadnych pętli ani wyraźnych dyrektyw OpenMP . Czy byłoby to możliwe w C (to znaczy bez użycia pętli i automatycznej równoległości)? Nie używam C, więc nie wiem.
źródło
icc
wykonuje również automatyczną równoległość. Dodałem plikicctest.c
do innych źródeł. Czy możesz go skompilować z tymi samymi opcjami, które użyłeś powyżej, uruchomić go i zgłosić czasy? Musiałem dodać instrukcję printf do mojego kodu, aby uniknąć optymalizacji wszystkiego przez gcc. To tylko szybki hack i mam nadzieję, że nie zawiera błędów!Myślę, że nie tylko interesujące jest to, jak kompilator optymalizuje kod dla nowoczesnego sprzętu. Zwłaszcza między GNU C i GNU Fortran generowanie kodu może być bardzo różne.
Rozważmy więc inny przykład, aby pokazać różnice między nimi.
Używając liczb zespolonych, kompilator GNU C generuje duży narzut dla prawie bardzo podstawowych operacji arytmetycznych na liczbie zespolonej. Kompilator Fortran daje znacznie lepszy kod. Rzućmy okiem na następujący mały przykład w Fortran:
daje (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Które mają 39 bajtów kodu maszynowego. Gdy rozważymy to samo w C.
i spójrz na wynik (zrobiony w taki sam sposób jak powyżej), otrzymujemy:
Które są również 39-bajtowym kodem maszynowym, ale odnoszą się do kroku funkcji 57, wykonuje odpowiednią część pracy i wykonuje żądaną operację. Mamy więc 27-bajtowy kod maszynowy do uruchomienia operacji wielu. Za tą funkcją odpowiada muldc3, którego
libgcc_s.so
kod maszynowy ma rozmiar 1375 bajtów. Spowalnia to znacznie kod i daje ciekawe wyniki przy korzystaniu z profilera.Kiedy implementujemy powyższe przykłady BLAS
zaxpy
i przeprowadzamy ten sam test, kompilator Fortran powinien dawać lepsze wyniki niż kompilator C.(Użyłem GCC 4.4.3 w tym eksperymencie, ale zauważyłem to zachowanie, które wydaje inne GCC).
Więc moim zdaniem nie myślimy tylko o równoległości i wektoryzacji, kiedy zastanawiamy się, który jest lepszym kompilatorem, musimy również sprawdzić, jak podstawowe rzeczy są tłumaczone na kod asemblera. Jeśli to tłumaczenie daje zły kod, optymalizacja może wykorzystywać te rzeczy tylko jako dane wejściowe.
źródło
complex.c
i dodałem go do kodu online. Musiałem dodać wszystkie wejścia / wyjścia, aby upewnić się, że nic nie jest zoptymalizowane. Dostaję telefon tylko,__muldc3
jeśli nie używam-ffast-math
. Ze-O2 -ffast-math
mam 9 linii inlined asemblerze. Czy możesz to potwierdzić?-ffast-math
) nie powinieneś używać Fortrana do obliczeń o złożonej wartości. Jak opisuję w aktualizacji mojego pytania-ffast-math
lub, bardziej ogólnie,-fcx-limited-range
zmusza gcc do korzystania z tych samych obliczeń o ograniczonym zakresie, które nie są zgodne z IEEE, co są standardowe w Fortran. Więc jeśli chcesz mieć pełny zakres wartości złożonych i poprawić Infs i NaNs, nie powinieneś używać Fortran ...Ludzie,
Uważam tę dyskusję za bardzo interesującą, ale zdziwiłem się, widząc, że zmiana kolejności pętli w przykładzie Matmula zmieniła obraz. Nie mam kompilatora Intel dostępnego na mojej bieżącej maszynie, więc używam gfortran, ale przepisałem pętle w mm_test.f90 na
zmieniłem całe wyniki dla mojej maszyny.
Wyniki synchronizacji poprzedniej wersji były następujące:
podczas gdy z potrójnymi pętlami ułożonymi ponownie, jak wyżej:
To jest gcc / gfortran 4.7.2 20121109 na procesorze Intel (R) Core (TM) i7-2600K @ 3.40GHz
Użyto flag kompilatora z pliku Makefile, który tu dostałem ...
źródło
To nie języki przyspieszają działanie kodu, chociaż pomagają. To kompilator, procesor i system operacyjny sprawiają, że kody działają szybciej. Porównywanie języków jest po prostu mylące, bezużyteczne i bez znaczenia. Nie ma to żadnego sensu, ponieważ porównujesz dwie zmienne: język i kompilator. Jeśli jeden kod działa szybciej, nie wiesz, ile to jest język ani ile to jest kompilator. Nie rozumiem, dlaczego społeczność informatyczna po prostu tego nie rozumie :-(
źródło