Gentoo Logo

Optymalizacja kompilacji

Spis treści:

1.  Wstęp

Czym są CFLAGS i CXXFLAGS?

CFLAGS i CXXFLAGS są zmiennymi środowiskowymi, które są używane przy kompilacji kodu przez kompilator gcc. Mówią one jakie przełączniki mają być użyte podczas kompilacji. Zmienna CFLAGS jest dla kodu napisanego w języku C, zaś CXXFLAS dla kodu napisanego w C++.

Mogą one przysłużyć się do zmniejszenia ilości informacji diagnostycznych dla programu, wzrostu poziomu ostrzeżeń o błędach i optymalizacji wygenerowanego kodu. Podręcznik GNU gcc zawiera kompletną listę z dostępnymi opcjami i opisem ich działania.

Jak ich używać?

CFLAGS i CXXFLAGS mogą być użyte na dwa sposoby. Po pierwsze przez program przy użyciu plików Makefile wygenerowanych przez automake.

Nie należy tego używać podczas instalacji pakietów z drzewa Portage. Od tego są zmienne CFLAGS i CXXFLAGS w /etc/make.conf. W ten sposób wszystkie pakiety zostaną skompilowane z danymi opcjami.

Listing 1.1: CFLAGS w /etc/make.conf

CFLAGS="-march=athlon64 -O2 -pipe"
CXXFLAGS="${CFLAGS}"

Jak widać, zmienna CXXFLAGS zawiera wszystkie opcje podane przez CFLAGS. Jest to bezpieczny sposób, który nigdy nie powinien zawieść. Nigdy nie powinno się dodawać innych opcji do CXXFLAGS.

Błędne mniemanie

Kiedy mówi się, że CFLAGS i CXXFLAGS mogą być bardzo efektywne, znaczy to, że produkują mniejszą i/lub szybszą wersję binarną. Ale mogą też osłabić funkcje kodu oraz powiększyć jego rozmiary, spowolnić czas jego wykonywania a nawet spowodować niepowodzenie jego kompilacji!

CFLAGS nie jest receptą na sukces. Nie stworzą systemu, który będzie się uruchamiał szybciej lub też skompilowany kod będzie zajmował mniej miejsca. Dodawanie większej ilości flag w celu optymalizacji zaowocuje efektem odwrotnym. Jest to pewna recepta na niepowodzenie.

W internecie można znaleźć informacje o tym, że agresywne flagi mogą wyrządzić więcej szkód w systemie niż pożytku. Należy pamiętać o tym, że powodem istnienia flag jest określony cel działania. To, że poszczególne opcje są dobre dla kawałka kodu, nie oznacza, że są dobre dla wszystkiego, co będzie instalowane w systemie!

Gotowi?

Skoro już jesteśmy świadomi ryzyka, spójrzmy na kilka bezpiecznych flag dla naszego systemu. Pozwoli to być pomocnym zawsze, kiedy zostanie zgłoszony problem na Bugzilli. Deweloperzy zwykle proszą o ponowną kompilację kodu z minimalnymi flagami, aby dowiedzieć się, czy problem faktycznie istnieje. Agresywne flagi mogą zrujnować kod.

2.  Optymalizacja

Podstawy

Celem flag jest stworzenie kodu idealnie dopasowanego do konfiguracji sprzętowej; powinien działać prawidłowo, a zarazem być mały i szybki jak tylko możliwe. Czasami te dwie cechy się wzajemnie wykluczają, więc trzeba trochę poeksperymentować z kombinacjami, aby były prawidłowe. Są one dostępne dla każdej architektury procesora. Agresywne flagi zostaną opisane w późniejszym etapie. Nie omówimy każdej opcji z podręcznika gcc, ponieważ są ich setki. Skupimy się na głównych, najczęściej używanych flagach.

Uwaga: Nie będąc pewnym do czego służy dana flaga, należy odwołać się do odpowiedniej sekcji Podręcznika gcc. W razie dalszych wątpliwości można również sprawdzić listę mailingową gcc.

-march

Najważniejszą opcją jest -march. Mówi ona kompilatorowi jaki powinien być kod wynikowy dla używanej architektury (arch) procesora. Różne rodzaje procesorów mają różne zdolności, wspierają różne instrukcje i mają różne rodzaje wykonywania kodu. Flaga -march ma za zadanie stworzenie kodu, który będzie specyficzny dla danego procesora, ze wszystkimi tymi zdolnościami, cechami, instrukcjami itp.

Nawet jeśli zmienna CHOST jest ustawiona w pliku /etc/make.conf i określa używaną architekturę, flaga -march powinna być użyta aby programy mogły zostać zoptymalizowane dla konkretnego procesora. Architektury x86 i x86-64 powinny używać flagi -march.

Aby dowiedzieć się, jaki rodzaj procesora jest w komputerze, którego używamy, należy wykonać polecenie:

Listing 2.1: Analiza informacji o procesorze

$ cat /proc/cpuinfo

Oglądnijmy -march w akcji. Przykład jest dla starszego procesora Pentium III

Listing 2.2: /etc/make.conf: Pentium III

CFLAGS="-march=pentium3"
CXXFLAGS="${CFLAGS}"

Ten przykład pochodzi z 64-bitowego procesora AMD.

Listing 2.3: /etc/make.conf: AMD64

CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"

Dostępne są również flagi -mtune oraz -mcpu. Jednak powinny zostać one użyte tylko wtedy, gdy opcja -march nie jest dostępna. Pewne architektury procesorów mogą wymagać -mtune lub nawet -mcpu. Niestety zachowanie gcc z użyciem tych flag nie jest identyczne na wszystkich architekturach.

Na procesorach x86 i x86-64, -march tworzy kod specyficzny dla tych architektur, używając wszystkich możliwych instrukcji i odpowiedniego ABI. Nie będzie kompatybilne dla starszych procesorów. Jeśli nie zamierzamy uruchamiać kodu na niczym innym jak nasz system, powinniśmy używać -march. Użycie -mtune można rozważyć w przypadku kompilacji dla starszego typu procesora takiego jak i386 lub i486. -mtune tworzy bardziej ogólny kod niż -march lecz w dalszym ciągu odpowiedni dla pewnego procesora. Nie należy używać -mcpu na systemach x86 i x86-64.

Inne architektury niż x86/x86-64 (takie jak Sparc, Alpha lub PowerPC) mogą wymagać użycia -mtune lub -mcpu zamiast -march. Na tych architekturach -mtune/-mcpu może czasami zachowywać się jak -march (na x86/x86-64) jedynie podając inną nazwę. Jeszcze raz: zachowanie gcc nie jest identyczne na wszystkich architekturach, więc należy sprawdzić stronę podręcznika gcc aby dowiedzieć się którą opcję użyć dla posiadanego procesora.

Uwaga: Dla konkretniejszych ustawień -march/-mtune/-mcpu należy przeczytać rozdział piąty Podręcznika instalacji Gentoo dla odpowiedniej architektury. Polecamy również przeczytać listę podręcznika gcc o opcjach specyficznych dla danej architektury gdzie jest dokładnie wytłumaczona różnica pomiędzy -march, -mcpu i -mtune.

-O

Następną opcją jest -O. Kontroluje ona całkowity poziom optymalizacji. Może ona spowodować, że kod będzie się kompilował nieco dłużej i zostanie użyte nieco więcej pamięci wraz z jej podnoszeniem.

Jest pięć opcji -O: -O0, -O1, -O2, -O3 oraz -Os. W /etc/make.conf powinna być użyta tylko jedna.

Z wyjątkiem -O0, każda następna opcja -O aktywuje kilka dodatkowych flag. O tym jakie flagi są aktywowane przy każdym poziomie -O i co powodują, można się dowiedzieć po przeczytaniu rozdziału o opcjach optymalizacji strony manuala gcc.

Przeanalizujmy każdy poziom optymalizacji:

  • -O0: Ten poziom (jest to litera "O" za którą jest cyfra zero) kompletnie wyłącza optymalizację i jest domyślna kiedy opcja -O nie jest określona w zmiennych CFLAGS i CXXFLAGS, co nie jest generalnie pożądane.
  • -O1: Bardzo podstawowy poziom optymalizacji. Kompilator będzie próbował budować szybszy i mniejszy kod bez tracenia na to czasu. Jest to naprawdę podstawowy poziom i nie powinien stanowić problemów podczas kompilacji.
  • -O2: Poziom wyżej od -O1. Jest to zalecany poziom optymalizacji jeżeli nie mamy specjalnych potrzeb. -O2 do -O1 doda kilka nowych flag. Z opcją -O2 kompilator będzie próbował zwiększyć wydajność kodu nie zwracając uwagi na rozmiar kodu wynikowego przy długim czasie kompilacji.
  • -O3: Najwyższy możliwy poziom optymalizacji i zarazem najbardziej ryzykowny. Z tą opcją kompilator będzie tracił dużo czasu na stworzenie kodu wynikowego. Generalnie opcja nie powinna być używana na systemach używających gcc w wersji 4.x. Zachowanie gcc znacznie się zmieniło od wersji 3.x, gdzie opcja -O3 miała prowadzić do trochę szybszego wykonania niż przy -O2. Przy wersji kompilatora gcc 4.x, kompilowany z użyciem -O3 kod będzie miał wielki rozmiar, a jego wykonanie będzie wymagało użycia większej ilości pamięci. Będzie również znacznie zwiększał przypadki niepowodzenia kompilacji i nieoczekiwane zachowanie programów. Używanie -O3 nie jest zalecane dla gcc 4.x.
  • -Os: Ten poziom będzie optymalizował kod pod względem wielkości. Aktywuje on wszystkie flagi z poziomu -O2, które nie biorą udziału w zwiększeniu wygenerowanego kodu. Może to być przydatne dla maszyn, które mają bardzo ograniczoną przestrzeń dyskową lub z procesorami o bardzo małej wielkości cache. Może jednak spowodować sporo problemów, ponieważ filtrowane przez wiele ebuildów z drzewa Portage. Używanie -Os nie jest zalecane.

Jak zostało wcześniej wspomniane, -O2 jest rekomendowanym poziomem optymalizacji. Jeśli pakiet zawiedzie podczas kompilacji, należy się upewnić, że nie jest używana opcja -O3. Przed zgłoseniem błędu należy spróbować zbudować pakiet z użyciem niższego poziomu optymalizacji takim jak -O1 lub nawet -O0 -g2 -ggdb.

-pipe

-pipe jest bezpieczną flagą. Nie ma ona wpływu na generowany kod, lecz przynosi zysk w prędkości kompilacji. Każe kompilatorowi używać potoków zamiast plików tymczasowych w różnych etapach kompilacji.

-fomit-frame-pointer

Jest to bardzo powszechna flaga stworzona do zmniejszania wielkości generowanego kodu. Jest ona włączona w każdym poziomie -O (poza -O0) na architekturach gdzie nie przeszkadza to przy debugowaniu (takich jak np. x86-64). Ponieważ strona podręcznika GNU gcc nie podaje wszystkich architektur, na których -O wymusza działanie tej flagi, czasem trzeba ją dodać własnoręcznie. Używanie tej flagi może powodować problemy podczas debugowania kodu.

Szczególne problemy sprawiają aplikacje napisane w Javie, chociaż nie jest ona jedynym językiem napiętnowanym przez tę flagę. Trzeba mieć na uwadze, że mimo tego, że flaga ogólnie pomaga, powoduje trudności podczas debugowania kodu; backtrace będzie bezużyteczne. Jeśli nie zamierzamy poddawać kodu operacjom debugowania i nie mamy w zmiennej CFLAGS innych flag, takich jak -ggdb, warto dodać -fomit-frame-pointer.

Ważne: Nie należy łączyć -fomit-frame-pointer z podobnie działającą flagą -momit-leaf-frame-pointer. Użycie drugiej flagi jest niepotrzebne, ponieważ -fomit-frame-pointer wykonuje swoją pracę prawidłowo. Ponadto, -momit-leaf-frame-pointer okazuje się negatywnie wpływać na jakość kodu.

-msse, -msse2, -msse3, -mmmx, -m3dnow

Te flagi uruchamiają ustawienia instrukcji SSE, SSE2, SSE3, MMX i 3DNow! dla architektur x86 i x86-64. Jest to użyteczne przede wszystkim przy multimediach, grach i zadaniach, wymagających intensywnych obliczeń matematycznych. Funkcje te są dostępne w nowszych modelach procesorów.

Ważne: Należy się upewnić, że dany procesor wspiera powyższe flagi. Można się tego dowiedzieć poprzez cat /proc/cpuinfo. Wyjście tego polecenia będzie zawierało wszystkie dodatkowe ustawienia. Trzeba mieć na uwadze, że pni jest inną nazwą dla SSE3.

Generalnie nie ma potrzeby dodawania jakiejkolwiek z tych flag, dopóki jest ustawiona odpowiednia opcja -march (np. -march=nocona zawiera -msse3). Kilka wartych uwagi wyjątków to nowsze procesory VIA i AMD64, które wspierają instrukcje niezawarte w -march (takie jak SSE3). Dla takich procesorów będzie wymagane dodanie flag, które będą odpowiadały wynikowi cat /proc/cpuinfo.

Uwaga: Należy zapoznać się z listą flag specyficznych tylko dla architektur x86 i x86-64, aby wiedzieć co aktywuje każda z tych ustawień. Gdy instrukcja jest podana, wtedy nie ma potrzeby jej wyszczególnienia. Będzie ona domyślnie włączona przy odpowiedniej fladze -march.

3.  FAQ Optymalizacji

Ale można osiągnąć lepsze wyniki przy użyciu -funroll-loops -fomg-optimize!

Niestety. Może się tylko wydawać, ponieważ ktoś nas przekonał, że wydajność rośnie z liczbą użytych flag. Agresywne flagi mogą tylko uszkodzić aplikacje. Nawet strona manuala gcc mówi, że użycie -funroll-loops i -funroll-all-loops tworzy kod większy i bardziej powolny. Po -ffast-math, -fforce-mem, -fforce-addr, są to kolejne mylnie używane flagi.

Prawdą jest, że są to bardzo agresywne flagi. Można się o tym przekonać w bardzo łatwy sposób: odwiedzając Forum Gentoo oraz Bugzillę. Szybko zauważymy że nie robią one nic dobrego!

Nie należy ich używać, ponieważ stworzą one spuchnięty kod o wielkich rozmiarach i słabej wydajności. Sprawią, że system będzie działał na krawędzi, a nasze bugi zostaną oznaczone jako INVALID lub WONTFIX.

Nie potrzeba tak niebezpiecznych flag. Nie należy ich używać. Lepsze od tego jest użycie podstawowych flag: -march, -O i -pipe.

Co z poziomami -O wyższymi niż 3?

Niektórzy użytkownicy chwalą się lepszą wydajnością przy użyciu -O4, -O9 itp. Prawdą jednak jest to, że poziomy wyższe niż 3 nie mają żadnego wpływu. Kompilator akceptuje CFLAGS takie jak -O4, lecz nie wykorzystuje ich przy budowie kodu. Jest to równoznaczne z użyciem -O3, nic więcej.

Więcej dowodów? Przeanalizuj kod źródłowy gcc:

Listing 3.1: Kod źródłowy -O

if (optimize >= 3)
    {
      flag_inline_functions = 1;
      flag_unswitch_loops = 1;
      flag_gcse_after_reload = 1;
      /* Allow even more virtual operators.  */
      set_param_value ("max-aliased-vops", 1000);
      set_param_value ("avg-aliased-vops", 3);
    }

Jak widać, każda wartość większa niż 3 jest traktowana jak -O3.

Co ze zbędnymi flagami?

Opcje CFLAGS i CXXFLAGS, które są zawarte w różnych poziomach -O są zbędne w /etc/make.conf. Czasami są one ignorowane, a czasami filtrowane lub zastępowane.

Filtracje i zastępowanie flag jest wykorzystywane przez wiele ebuildów w drzewie Portage. Zazwyczaj jest to robione z powodu niepowodzenia przy kompilacji z określonym poziomem optymalizacji -O lub kiedy kod jest zbyt wrażliwy na wszelkie dodatkowe flagi. Ebuild będzie filtrował niektóre CFLAGS i CXXFLAGS lub zastąpi -O innym poziomem.

Podręcznik Dewelopera Gentoo tworzy szkic gdzie i jak filtrowanie i zastępowanie flag działa.

Można ominąć filtrowania -O poprzez niepotrzebne dodawanie flag pewnych poziomów optymalizacji, takich jak -O3

Listing 3.2: Zbędne opcje w CFLAGS

CFLAGS="-O3 -finline-functions -funswitch-loops"

Nie jest to mądre. CFLAGS jest filtrowane z konkretnego powodu! Kiedy flagi są filtrowane, znaczy to, że są niebezpieczne do budowy pakietu. Naprawdę, nie jest bezpieczne budowanie całego systemu z -O3 jeśli jakaś flaga z tego poziomu optymalizacji będzie tworzyła problemy z danymi pakietami. Zaufaj deweloperom. Filtrowanie i zastępowanie flag jest robione dla bezpieczeństwa naszych danych. Jeśli ebuild określa alternatywną flagę, nie należy tego obchodzić w żaden sposób!

Po zbudowaniu pakietu z nieakceptowanymi flagami, najczęściej pogłębiamy się w problemach. Kiedy zgłaszamy błąd na Bugzilli, użyte flagi z /etc/make.conf są widoczne. Zostaniemy wtedy powiadomieni, aby przebudować dany pakiet bez niepotrzebnych flag. Można sobie tego oszczędzić poprzez ich nieużywanie i niezakładanie, że wie się lepiej niż deweloperzy.

Co z LDFLAGS?

Deweloperzy Gentoo ustawili w profilu podstawowe i bezpieczne wartości LDFLAGS dlatego nie ma potrzeby ich definiowania.

Czy można używać flag dla pojedyńczych pakietów?

W tej chwili nie ma żadnej metody wspieranej przez nas, aby używać CFLAGS lub innych zmiennych dla jednego pakietu. Istnieje jednak kilka metod na zmuszenie Portage do takiego działania.

Nie powinniśmy jednak zmuszać Portage do używania flag dla jednego pakietu, ponieważ takie działanie nie jest w żaden sposób wspierane i najprawdopodobniej skomplikuje wysyłanie ewentualnych raportów dotyczących błędów. Należy ustawić odpowiednie zmienne w pliku /etc/make.conf tak, aby były one używane w całym systemie.

4.  Zasoby

Poniższe źródła są pomocne dla dalszego zrozumienia optymalizacji:



Drukuj

Zaktualizowano 21 lipca 2008

Oryginalna wersja tego dokumentu została po raz ostatni zaktualizowana 19 października 2009. Jeśli chcesz pomóc w aktualizacji tego dokumentu do najnowszej wersji, skontaktuj się z Łukaszem Damentko, koordynatorem polskiego projektu tłumaczeń dokumentacji Gentoo.

Podsumowanie: Wstęp do optymalizacji kompilowanego kodu przy użyciu bezpiecznych, rozsądnych CFLAGS oraz CXXFLAGS. Opisuje również teorię optymalizacji.

Joshua Saddler
Autor

Dawid Węgliński
Tłumacz

Donate to support our development efforts.

Support OSL
Gentoo Centric Hosting: vr.org
Tek Alchemy
SevenL.net
Global Netoptex Inc.
Bytemark
Online Kredit Index
Copyright 2001-2009 Gentoo Foundation, Inc. Questions, Comments? Contact us.