Zapewne większość ludzi związanych z programowaniem zna klasyczny już epizod Compiling z komiksu xkcd ilustrujący długi czas kompilacji. Prezentuje się on następująco:

Źródło: https://xkcd.com/303/

Obrazek porusza problem czasu kompilacji występującego w językach kompilowanych takich jak C oraz C++. Długi czas kompilacji oznacza dodatkowy koszt z dwóch powodów:

  1. zużycie dodatkowej energii elektrycznej,
  2. nieefektywne wykorzystanie czasu programisty.

Ktoś mógłby zauważyć, że podczas kompilacji można wykonywać inne czynności związane z rozwiązywanym zadaniem. Oczywiście można, ale powoduje to odsunięcie uwagi od aktualnie rozwiązywanego problemu. Wynika to z natury człowieka oraz trudności w realizacji wielu zadań równocześnie1.

Dlaczego kod się długo kompiluje?

W języku C++ występują dwa rodzaje plików. Są to pliki nagłówkowe (header files) oraz pliki źródłowe (source files). W plikach nagłówkowych znajdują się deklaracje bytów takich jak typy, funkcje i klasy natomiast w plikach nagłówkowych znajdują się implementacje funkcji oraz klas. Dodatkowo w języku zawarto dyrektywę #include służącą do dołączania zawartości plików.

Kompilator kompiluje wyłącznie pliki źródłowe2. Na wstępie (preprocesor) w miejsce dyrektyw #include wkleja zawartość wskazywanych plików tworząc w ten sposób jeden plik w formie translation unit3 zawierający wszystkie potrzebne (za-include-owane) fragmenty kodu źródłowego gotowy do dalszego przetworzenia. Może zdarzyć się sytuacja, że dany fragment kodu wskazany jest w wielu plikach źródłowych, a przez to wiele razy (zbędnie) kompilowany.

Taka sytuacja ma miejsce szczególnie w dwóch przypadkach:

  1. podczas umieszczania implementacji funkcji i metod w plikach nagłówkowych,
  2. podczas używania szablonów oraz makr4.
długi czas kompilacji może spowodować znaczny ubytek włosów

Co robić, jak żyć i jak zredukować czas kompilacji?

Istnieje kilka sposobów radzenia sobie z długim czasem kompilacji:

  • reorganizacja kodu źródłowego,
  • opcje kompilacji,
  • zastosowanie wydajniejszego sprzętu,
  • wdrożenie zarządcy pakietów (package manager).

Lista została subiektywnie sporządzona w kolejności od najbardziej efektywnego do najmniej efektywnego rozwiązania.

Package Manager

Jednym z wiodących zarządców pakietów jest oprogramowanie Conan. Głównym jego celem jest zapewnienie odpowiednich wersji bibliotek wymaganych przez projekt. W celu przyspieszenia kompilacji Conan może być wykorzystany do przechowywania wymaganych bibliotek bez konieczności ich budowania na stacjach lokalnych. Dodatkowo czasami w projekcie można wyodrębnić lokalne biblioteki, które mogą być przechowywane przez zarządcę w formie już skompilowanej.

Wsparcie sprzętowe

W niedalekiej przeszłości wystąpiły dwa kamienie milowe istotnie wpływające na redukcję czasu kompilacji:

  1. wprowadzenie oraz upowszechnienie architektury wielordzeniowej umożliwiającej kompilację równoległą5,
  2. wprowadzenie napędów półprzewodnikowych istotnie redukujących czas dostępu do pamięci stałej podczas kompilacji.

Innymi parametrami wpływającymi długość kompilacji są między innymi: taktowanie zegara procesora, rozmiar dostępnej pamięci podręcznej (cache), wielkość pamięci o dostępie swobodnym (Random Access Memory) i tym podobne.

Niestety dalsza modernizacja sprzętu nie zawsze przynosi oczekiwane rezultaty. Wówczas należy inne sposoby.

Opcje kompilacji

Istnieje przynajmniej kilka opcji kompilacji wspomagających ten proces. Jest to między innymi pamięć podręczna kompilatora, kompilacja równoległa oraz kompilacja rozproszona.

Pamięć podręczna kompilacji może zostać aktywowana między innymi dzięki programowi ccache. Program wspomaga kompilację poprzez przechowywanie artefaktów z poprzednich kompilacji i ponowne ich użycie.

Kompilacja równoległa jest to budowanie projektu na lokalnej maszynie z wykorzystaniem więcej niż jednego procesu kompilacji. Wiodące kompilatory umożliwiają wskazanie liczby procesów kompilacji. Przykładowo jest to parametr -j lub -m. Kompilacja rozproszona ma miejsce w przypadku kompilacji projektu przy użyciu wielu maszyn najczęściej zorganizowanych w sieci lokalnej. Przykładem takiego oprogramowania jest pakiet icecc systemu operacyjnego Ubuntu.

Kompilacja w wyżej opisanych trybach jest ograniczona organizacją pakietów w projekcie, przez co nie wszystkie translation units będą budowane w tym samym czasie. Kompilacja będzie odbywała się szeregowo w sytuacji relacji zależności pomiędzy pakietami.

Organizacja kodu, a długość kompilacji

Techniki analizy oraz rozwiązywania problemów kompilacji zasługują na osobny wpis. Najpopularniejsze techniki redukcji czasu kompilacji to:

  • ograniczenie #include-ów z plików nagłówkowych do plików źródłowych,
  • podział dużych plików nagłówkowych, modułów oraz bibliotek na mniejsze elementy,
  • ograniczenie stosowania szablonów oraz wykorzystanie techniki explicit template specialization,
  • wykorzystanie wzorca PImpl,
  • zastosowanie techniki unity build6.

Ważne jest aby podczas prób redukcji czasu kompilacji mierzyć wpływ wprowadzanych zmian na czas kompilacji, ponieważ niektóre techniki w określonych sytuacjach mogą mieć marginalny wpływ, a znacząco komplikować strukturę kodu.

Twój zespół marnuje czas tłumacząc się kompilacją, a Jenkins jest ciągle zapchany zadaniami?

  1. więcej na temat zdolności mózgu do realizacji wielu zadań można przeczytać na Wikipedii ↩︎
  2. celowo pominięto takie mechanizmy jak precompiled headers ↩︎
  3. opis translation unit przedstawiony jest między innymi na Wikipedii ↩︎
  4. jest do szczególny przypadek punktu pierwszego ↩︎
  5. opis zagadnienia obliczeń równoległych dostępny jest na Wikipedii ↩︎
  6. nie mylić z Unity Game Engine; szczegóły dostępne są na Wikipedii ↩︎


Leave a Reply

Your email address will not be published. Required fields are marked *