Compiler explorer – Twój najlepszy przyjaciel w rozmowach o wydajności kodu

W artykułach o sprytnym kodzie i mikrooptymalizacjach postawiłem tezę, że często większa wydajność kodu, którą usprawiedliwiamy zmniejszenie czytelności, jest fikcją. Aby to stwierdzić potrzebujemy danych. Tymi danymi są czas wykonania aplikacji i kod asemblerowy generowany przez kompilator. Dzisiaj opiszę narzędzie przeglądarkowe pozwalające nam analizować właśnie kod asemblerowy. A jest nim Compiler Explorer.

Czym jest Compiler Explorer?

Compiler Explorer, znany także jako Godbolt ze względu na adres strony i nazwisko autora, to kompilator online. Możemy tam wpisać fragment kodu w C, czy C++ i zobaczyć jaki wygeneruje kod asemblerowy dla wybranego kompilatora i flag.

Wygląda to mniej więcej tak:

https://godbolt.org/z/nAGHdu

Kod asemblerowy odpowiedzialny za poszczególne fragmenty kodu wejściowego jest zaznaczony na odpowiednie kolory. Możemy sobie z listy wybierać inne kompilatory, czy wpisywać dodatkowe opcje kompilacji jak na przykład optymalizację. Dzięki temu jesteśmy w stanie szybko sprawdzić jak zmienia się kod wynikowy.

Jeżeli nie jesteśmy biegli w znaczeniu instrukcji asemblerowych, możemy sobie na nie najechać myszką i przeczytać wyskakujący opis.

Lista wspieranych kompilatorów jest całkiem spora i zawiera choćby gcc, clang, czy msvc. Mamy też do wyboru całkiem sporo architektur procesorów. Są oczywiście x86 i ARM, ale również AVR, RISC-V, MIPS, a nawet Arduino i Raspbian.

Mamy do wyboru również inne języki niż C i C++. Tak, wśród nich jest Ada. Poza tym jest też na przykład Rust.

A komu to potrzebne?

Developerowi chcącemu sprawdzić wydajność kodu. Przypadków, kiedy sprawdzenie czegoś szybko w Godbolcie może być całkiem sporo. Na przykład mamy w zespole dyskusję na temat sensu stosowania jakiejś konstrukcji w kodzie. Czy mniej czytelny kod faktycznie skutkuje zwiększeniem wydajności? Dopóki nie mamy dowodów nie jesteśmy w stanie odpowiedzieć na to pytanie.

Czy dana optymalizacja ma sens?

W artykule o sprytnym kodzie na przykład podałem kilka przykładów, które mogą wywołać podobną dyskusję. Weźmy na przykład funkcję my_string z tamtego artykułu. Okazuje się, że na x86 64-bit i optymalizacji O2 obie wersje dają ten sam kod wynikowy:

https://godbolt.org/z/-dtdKZ

Z kolei jeżeli zamienimy optymalizację na -O0, okazuje się, że bardziej rozwlekła wersja w C daje nawet krótszy kod asemblerowy (możemy otworzyć sobie Diff view, żeby zobaczyć dokładnie różnice):

https://godbolt.org/z/fwQ__B

To jeszcze nie znaczy, że mniej instrukcji się szybciej wykona (szczególnie w przypadku pętli). Aby mieć pewność należy wykonać benchmark. Jednak dla optymalizacji O2 wiemy na pewno, że output jest taki sam. Są to twarde dane pozwalające nam podjąć decyzję projektową.

Przy okazji tego przykładu mogliśmy również sobie zobaczyć jak kod wynikowy różni się między poziomami optymalizacji O0 i O2.

Czy używam FPU?

Inny ciekawy przykład pokazujący jak Godbolt może się przydać odnosi się do aktywacji operacji zmiennoprzecinkowych ze wsparciem hardware’u, o czym pisałem tutaj:

Mamy kompilator ARM GCC 8.2 i kompilujemy na Cortex-M4. W pierwszym przykładzie możemy zobaczyć jak domyślnie kompilator traktuje liczby zmiennoprzecinkowe jako typ double, jeżeli nie dodamy suffixu f:

https://godbolt.org/z/BN3Byz

W efekcie zamiast operacji zmiennoprzecinkowych VLDR, VDIV itd. mamy w prawym panelu skoki do software’owej emulacji floatów __aeabi_i2d, __aeabi_ddiv.

W drugiej wersji mamy liczbę zmiennoprzecinkową bez suffixu, ale za to po prawej stronie używamy dodatkowej flagi kompilacji -fsingle-precision-constant mówiącej, żeby domyślnie używać float zamiast double:

https://godbolt.org/z/xEHFqG

Jeżeli sprawdzamy takie rzeczy na kompilatorze online, a nie kompilujemy za każdym razem całego projektu, możemy zaoszczędzić dużo czasu. Oczywiście później na docelowym projekcie też warto sprawdzić instrukcje asemblerowe. Ale przynajmniej oszczędzimy sobie kompilacji podczas nieudanych prób wyboru odpowiedniego zestawu flag.

Bardziej zaawansowany przykład

Jeżeli chodzi o optymalizacje, jeszcze większe pole do popisu mamy używając C++. W wielu przypadkach pojedyncza linijka może kryć w sobie wiele operacji asemblerowych. Na przykład jeżeli robimy kopię zamiast move. Dlatego Compiler Explorer powstał właśnie z myślą o społeczności C++.

Jeżeli chcecie zobaczyć Compiler Explorer w akcji, a przy okazji jak nowoczesny C++ pomaga tworzyć kod zarówno optymalny jak i zrozumiały, polecam poniższą prezentację:

Dodatkowe informacje

Więcej informacji na temat projektu daje nam sam autor – Matt Godbolt:

Tutorial:

Prezentacja o Compiler Explorerze z CppCon:

1 Comment

  1. Hej! Świetny wpis i ciekawie dobrane przykłady. Też lubię Godbolt i z czasów pracy w C++ pamiętam, że nierzadko podczas code review argument stanowiły linki do tego serwisu, dowodzące nieoptymalność zastosowanego rozwiązania. ;D

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *