Oto kilka aktualnych, choć wąskich ustaleń z GCC 4.7.2 i Clang 3.2 dla C ++.
AKTUALIZACJA: Załączono poniżej porównanie GCC 4.8.1 v clang 3.3.
AKTUALIZACJA: Dołączono do tego porównanie GCC 4.8.2 v clang 3.4.
Utrzymuję narzędzie OSS, które jest zbudowane dla Linuksa z GCC i Clang oraz z kompilatorem Microsoft dla Windows. Narzędzie, coan, jest preprocesorem i analizatorem plików źródłowych C / C ++ i takich linii kodowych: jego profil obliczeniowy specjalizuje się w parsowaniu rekurencyjnym i obsłudze plików. Dział rozwoju (do którego odnoszą się te wyniki) obejmuje obecnie około 11 KB LOC w około 90 plikach. Jest teraz kodowany w C ++, który jest bogaty w polimorfizm i szablony, ale wciąż jest pogrążony w wielu łatach przez swoją nie tak daleką przeszłość w zhakowanych razem C. Semantyka ruchu nie jest wyraźnie wykorzystywana. Jest jednowątkowy. Nie poświęciłem żadnego poważnego wysiłku na jego optymalizację, podczas gdy „architektura” pozostaje w dużej mierze do zrobienia.
Użyłem Clanga przed 3.2 tylko jako eksperymentalny kompilator, ponieważ pomimo doskonałej prędkości kompilacji i diagnostyki, jego obsługa standardu C ++ 11 pozostawała w tyle za współczesną wersją GCC pod względem wykonywanym przez Coana. W wersji 3.2 luka ta została zamknięta.
Moja wiązka testowa systemu Linux do bieżących procesów programistycznych Coan z grubsza 70 000 plików źródłowych w mieszaninie przypadków analizatora składni z jednym plikiem, testy warunków skrajnych zużywa tysiące plików i testy scenariuszy z plikami mniejszymi niż 1 KB. Oprócz raportowania wyników testu, uprząż gromadzi i wyświetla sumy zużytych plików i czasu pracy zużytego w Coan (po prostu przekazuje każdą linię poleceń Coan do time
polecenia Linux i przechwytuje i sumuje zgłoszone liczby). Czasy są pochlebne przez fakt, że dowolna liczba testów, które wymagają 0 mierzalnego czasu, zsumuje się do 0, ale wkład takich testów jest znikomy. Statystyki czasu są wyświetlane na końcu w make check
następujący sposób:
coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.
Porównałem wydajność wiązki testowej między GCC 4.7.2 a Clang 3.2, przy czym wszystkie rzeczy były takie same, z wyjątkiem kompilatorów. Począwszy od wersji Clang 3.2, nie wymagam już żadnego preprocesora rozróżnienia między traktami kodu, które GCC będzie kompilować, a alternatywami Clanga. W każdym przypadku zbudowałem tę samą bibliotekę C ++ (GCC) i przeprowadziłem wszystkie porównania kolejno w tej samej sesji terminalowej.
Domyślny poziom optymalizacji dla mojej wersji wydania to -O2. Z powodzeniem testowałem również kompilacje w -O3. Testowałem każdą konfigurację 3 razy jeden po drugim i uśredniłem 3 wyniki, z następującymi wynikami. Liczba w komórce danych to średnia liczba mikrosekund zużywanych przez plik wykonywalny Coana do przetworzenia każdego z plików wejściowych ~ 70K (odczyt, parsowanie i zapisywanie danych wyjściowych oraz diagnostyka).
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|
Każda konkretna aplikacja najprawdopodobniej ma cechy, które działają niesprawiedliwie w stosunku do mocnych i słabych stron kompilatora. Rygorystyczne testy porównawcze wykorzystują różnorodne aplikacje. Mając to na uwadze, godnymi uwagi cechami tych danych są:
- Optymalizacja -3 była nieznacznie szkodliwa dla GCC
- Optymalizacja O3 była bardzo korzystna dla Clanga
- Przy optymalizacji -O2 GCC był szybszy niż Clang tylko dzięki wąsowi
- Przy optymalizacji -O3 Clang był znacznie szybszy niż GCC.
Kolejne interesujące porównanie dwóch kompilatorów pojawiło się przypadkiem wkrótce po tych ustaleniach. Coan swobodnie stosuje inteligentne wskaźniki, a jeden z nich jest mocno wyćwiczony w obsłudze plików. Ten konkretny typ inteligentnego wskaźnika został wpisany we wcześniejszych wersjach ze względu na różnicowanie kompilatora, aby był std::unique_ptr<X>
skonfigurowany, jeśli skonfigurowany kompilator miał wystarczająco dojrzałe wsparcie dla jego użycia jako takiego, a poza tym std::shared_ptr<X>
. Odchylenie std::unique_ptr
było głupie, ponieważ wskaźniki te faktycznie zostały przeniesione, ale std::unique_ptr
wyglądały jak opcja montera do zastąpienia
std::auto_ptr
w momencie, gdy warianty C ++ 11 były dla mnie nowością.
W trakcie eksperymentalnych kompilacji w celu oceny ciągłej potrzeby tego i podobnego różnicowania w Clang 3.2, nieumyślnie zbudowałem,
std::shared_ptr<X>
gdy miałem zamiar budować std::unique_ptr<X>
, i byłem zaskoczony, że wynikowy plik wykonywalny, z domyślną optymalizacją -O2, był najszybszy I widziałem, czasem osiągając 184 msek. na plik wejściowy. Przy tej jednej zmianie w kodzie źródłowym były to odpowiednie wyniki;
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |
Ważnymi punktami tutaj są:
- Żaden kompilator nie korzysta teraz w ogóle z optymalizacji -O3.
- Clang pokonuje GCC równie ważne na każdym poziomie optymalizacji.
- Na wydajność GCC jedynie nieznaczny wpływ ma zmiana typu inteligentnego wskaźnika.
- Na wydajność Clanga -O2 istotny wpływ ma zmiana typu inteligentnego wskaźnika.
Przed i po zmianie typu inteligentnego wskaźnika Clang jest w stanie zbudować znacznie szybszy plik wykonywalny Coana przy optymalizacji -O3 i może zbudować równie szybszy plik wykonywalny przy -O2 i -O3, gdy ten typ wskaźnika jest najlepszy - std::shared_ptr<X>
- dla pracy.
Oczywistym pytaniem, którego nie jestem kompetentny do skomentowania, jest to, dlaczego
Clang powinien być w stanie znaleźć przyspieszenie o 25% O2 w mojej aplikacji, gdy często używany typ inteligentnego wskaźnika zmienia się z unikalnego na współdzielony, podczas gdy GCC jest obojętny do tej samej zmiany. Nie wiem też, czy powinienem kibicować, czy wygłupiać się odkryciem, że optymalizacja Clang -O2 kryje w sobie tak ogromną wrażliwość na mądrość moich mądrych wskazówek.
AKTUALIZACJA: GCC 4.8.1 v clang 3.3
Odpowiednie wyniki są teraz:
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |
Fakt, że wszystkie cztery pliki wykonywalne zajmują teraz znacznie więcej czasu niż poprzednio, aby przetworzyć 1 plik, nie ma wpływu na wydajność najnowszych kompilatorów. Wynika to z faktu, że późniejsza gałąź programistyczna aplikacji testowej nabrała w międzyczasie wielu parsowania i szybko za to płaci. Istotne są tylko stosunki.
Najważniejsze kwestie nie są teraz zaskakująco nowe:
- GCC jest obojętny na optymalizację -O3
- clang korzysta bardzo nieznacznie z optymalizacji -O3
- clang pokonuje GCC o podobnie ważny margines na każdym poziomie optymalizacji.
Porównując te wyniki z wynikami dla GCC 4.7.2 i clang 3.2, wyróżnia się, że GCC cofnęło około jednej czwartej przewagi clanga na każdym poziomie optymalizacji. Ponieważ jednak aplikacja testowa została w międzyczasie bardzo rozbudowana, nie można tego z pewnością przypisać do nadrabiania zaległości w generowaniu kodu GCC. (Tym razem zauważyłem migawkę aplikacji, z której uzyskano taktowanie i mogę z niej ponownie korzystać.)
AKTUALIZACJA: GCC 4.8.2 v clang 3.4
Ukończyłem aktualizację dla GCC 4.8.1 v Clang 3.3, mówiąc, że będę trzymał się tej samej migawki Coan w celu dalszych aktualizacji. Ale zamiast tego zdecydowałem się przetestować tę migawkę (wersja 301) i najnowszą migawkę programistyczną, która przeszła pomyślnie przez zestaw testów (wersja 619). To daje trochę długości geograficznej, a miałem inny motyw:
W moim pierwotnym poście zauważyłem, że nie poświęciłem żadnego wysiłku na optymalizację coana pod kątem szybkości. Tak było nadal od wersji rev. 301. Jednak po tym, jak wbudowałem przyrząd do pomiaru czasu w uprząż testową Coana, za każdym razem, gdy uruchamiałem zestaw testowy, wpatrywał się we mnie wpływ najnowszych zmian na wydajność. Widziałem, że często był on zaskakująco duży i że trend był bardziej negatywny, niż czułem, że zasługują na mnie korzyści z funkcjonalności.
Przez rev. 308 średni czas przetwarzania na plik wejściowy w pakiecie testowym znacznie wzrósł ponad dwukrotnie od czasu pierwszego opublikowania tutaj. W tym momencie dokonałem zwrotu w mojej 10-letniej polityce nie zawracania sobie głowy wydajnością. W intensywnym szeregu zmian zawsze brano pod uwagę wydajność do 619, a duża liczba z nich poszła wyłącznie na przepisanie kluczowych nośników na zasadniczo szybszych liniach (choć bez użycia do tego żadnych niestandardowych funkcji kompilatora). Byłoby ciekawie zobaczyć reakcję każdego kompilatora na ten zwrot,
Oto znana już macierz czasowa dla najnowszych dwóch kompilatorów kompilacji rev.301:
coan - rev.301 wyniki
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|
Historia tutaj jest tylko nieznacznie zmieniona z GCC-4.8.1 i Clang-3.3. Pokazywanie GCC jest trochę lepsze. Clang's jest trochę gorszy. Hałas mógłby to wyjaśnić. Clang wciąż wychodzi przed siebie -O2
i -O3
marże, które nie miałyby znaczenia w większości aplikacji, ale miałyby znaczenie dla kilku.
A oto macierz dla rev. 619.
coan - rev.619 wyników
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|
Biorąc obok siebie cyfry 301 i 619, wypowiada się kilka punktów.
Chciałem napisać szybszy kod, a oba kompilatory zdecydowanie potwierdzają moje wysiłki. Ale:
GCC spłaca te wysiłki o wiele hojniej niż Clang. Przy -O2
optymalizacji Clang 619 kompilacji jest 46% szybszy niż 301 kompilacji: przy -O3
poprawie Clanga wynosi 31%. Dobrze, ale na każdym poziomie optymalizacji wersja 619 GCC jest ponad dwa razy szybsza niż 301.
GCC bardziej niż odwraca dawną wyższość Clanga. Na każdym poziomie optymalizacji GCC bije teraz Clanga o 17%.
Zdolność Clanga w kompilacji 301 do uzyskania większej dźwigni niż GCC z -O3
optymalizacji zniknęła w kompilacji 619. Żaden kompilator nie zyskuje znacząco -O3
.
Byłem wystarczająco zaskoczony tym odwróceniem losu, że podejrzewałem, że mógłbym przypadkowo stworzyć powolną kompilację samego klangu 3.4 (ponieważ zbudowałem go ze źródła). Więc ponownie przeprowadziłem test 619 z zapasem mojej dystrybucji Clang 3.3. Wyniki były praktycznie takie same jak dla 3.4.
Jeśli chodzi o reakcję na zawrót: pod tym względem Clang radził sobie znacznie lepiej niż GCC z szybkością wyciskania z mojego kodu C ++, kiedy nie dawałem mu żadnej pomocy. Kiedy postanowiłem pomóc, GCC wykonało znacznie lepszą robotę niż Clang.
Nie podniosę tej obserwacji do zasady, ale czerpię naukę, że „Który kompilator produkuje lepsze pliki binarne?” jest pytaniem, które nawet jeśli określisz zestaw testów, dla którego odpowiedź powinna być względna, nadal nie jest jednoznaczną kwestią po prostu określania czasu plików binarnych.
Czy Twój lepszy plik binarny jest najszybszym plikiem binarnym, czy to ten, który najlepiej rekompensuje tanio spreparowany kod? A może najlepiej rekompensuje drogo
spreparowany kod, który priorytetem jest łatwość konserwacji i ponowne wykorzystanie nad prędkością? Zależy to od charakteru i względnej wagi motywów tworzenia pliku binarnego oraz ograniczeń, na podstawie których to robisz.
W każdym razie, jeśli bardzo zależy ci na budowaniu „najlepszych” plików binarnych, lepiej sprawdzaj, w jaki sposób kolejne iteracje kompilatorów dostarczają ideę „najlepszej” nad kolejnymi iteracjami twojego kodu.