2004.08_Flagi sterujące optymalizacją w GCC_[Programowanie].pdf

(394 KB) Pobierz
439034444 UNPDF
kompilacja programów
Flagi sterujące
optymalizacją w GCC
Grzegorz Niewęgłowski
i jego możliwości opty-
malizacji kodu, jak rów-
nież samej kompilacji,
krąży w środowisku linuksowym wiele
informacji, często sprzecznych. W poniż-
szym artykule przedstawię podstawowe
fakty dotyczące optymalizacji GCC.
Opcje sterujące optymalizacją
w GCC można z grubsza podzielić na
dwie grupy: optymalizację pod konkret-
ną architekturę oraz optymalizację szcze-
gółową, przy której decyduje się o takich
niuansach, jak redukowanie zbędnych
pętli w kodzie, usuwanie niewykorzy-
stanych zmiennych, sterowanie użyciem
rejestrów procesora itp. Takie opcje GCC
nazywa się zwykle flagami. Używanie
flag może mieć wpływ na stabilność
i poprawność działania programów ze
względu na błędy w programach lub
samym kompilatorze. Z tego powodu,
jeśli zauważysz jakieś błędne zachowa-
nia, spróbuj skompilować kod jeszcze raz,
bez własnych optymalizacji, a następnie
stopniowo dodawać poszczególne flagi,
aż znajdziesz winowajcę.
Flaga -march powoduje wykorzystanie
wszystkich cech specyficznych danego
modelu procesora, nawet jeśli zrywa to
kompatybilność ze starszymi modelami,
np. zezwala na tworzenie kodu używa-
jącego dodatkowych rejestrów procesora
czy rozszerzonych zestawów instrukcji,
np. MMX. Użycie flagi -march powoduje
od razu ciche włączenie bliźniaczej flagi
-mtune . Ramka Wybór architektury proce-
sora w GCC (3.4.0) pokazuje zależność
Wybór architektury
procesora w GCC (3.4.0)
Procesor 386: -march=i386
Procesor 486: -march=i486
Procesor 586 (Pentium): -march=i586
lub -march=pentium
(te dwie opcje są równoznaczne)
WinChip: -march=winchip-c6 ,
-march=winchip2
Via C3: -march=c3 , -march=c3-2
Procesory Intel:
Pentium MMX: -march=pentium-mmx
Pentium Pro (pierwsze i686):
-march=i686 lub -march=pentium-
pro (te dwie opcje są równoznaczne)
Procesor Pentium2: -march=pentium2
Procesor Pentium3: -march=pentium3
Procesor Pentium4: -march=pentium4
Na płycie CD/DVD
Na płycie CD/DVD znajdują się
pakiety źródłowe nowego GCC.
Wybór typu procesora
Typ procesora wybiera się za pomocą
dwóch flag: -mtune= oraz -march= .
Flaga -mtune (znana również jako
-mcpu ) wybiera schemat kolejkowania
instrukcji procesora. Odpowiednio ukła-
dając kolejność napływania rozkazów do
procesora, można czasem przyspieszyć
ich realizację. Już procesory Pentium
potrafiły w pewnych sytuacjach wykony-
wać dwa rozkazy równolegle. Takie zmie-
nianie kolejności rozkazów nie zrywa
kompatybilności z innymi modelami
procesorów – one po prostu nie odniosą
z tego powodu żadnych korzyści.
Procesory AMD:
Procesor K6: -march=k6
Procesor K6-2: -march=k6-2
Procesor K6-3: -march=k6-3
Zwykły Athlon: -march=athlon
Athlon Thunderbird: -march=athlon-
tbird
Athlon4: -march=athlon-4
AthlonXP: -march=athlon-xp
AthlonMP: -march=athlon-mp
Rodzina AMD64 - do wyboru: k8 ,
opteron , athlon64 , athlon-fx
O autorze
Autor od kilku lat używa Linuksa
jako jedynego systemu opera-
cyjnego. Aktywnie uczestniczy
w polskich grupach dyskusyj-
nych poświęconych Linuksowi,
jak również prowadzi nieduży
internetowy kącik dotyczący
zagadnień związanych z tym
systemem. Kontakt z autorem:
autorzy@linux.com.pl
50 sierpień 2004
W okół kompilatora GCC
439034444.009.png 439034444.010.png 439034444.011.png 439034444.012.png
 
gcc
kompilacja programów
Nie ma sensu używanie -O3 przy kompi-
lacji całego systemu. Pamiętajmy też, że
-O3 powoduje przyrost objętości kodu,
co może być fatalne dla procesorów
z niewielką pamięcią podręczną ( cache ),
takich jak Celerony czy Durony.
Czasem widuje się flagi typu -O6 lub
nawet -O99 . Nie są to żadne dodatkowe
tajne optymalizacje, bo aktualne GCC
nie obsługują niczego mocniejszego niż
-O3 . Podając wyższe wartości, GCC i tak
użyje -O3 .
Jeśli w wywołaniu GCC podamy
kilka wartości -Ox, to GCC użyje tej
podanej najpóźniej.
Inne popularne lagi
GCC pozwala na drobiazgowe sterowanie
optymalizacją przy użyciu kilkudziesięciu
flag, z których jedynie kilka jest wartych
uwagi.
Rysunek 1. Przykładowe zależności między optymalizacją a wielkością kompilatu
i wydajnością
-ffast-math
Ta flaga może spowodować (niezbyt
duże) przyspieszenie wykonywania nie-
których obliczeń matematycznych (np.
funkcji trygonometrycznych), ale dzieje
się to poprzez naginanie standardów
IEEE/ISO, dotyczących takich obliczeń.
Jeśli autor kodu zakładał pełną zgod-
ność funkcji matematycznych z wyżej
wymienionymi standardami, to kod
może zacząć produkować błędne wyniki.
Gdy to sam autor kodu zaleca używanie
tej flagi (np. umieszczając ją w pliku
Makefile ), to znaczy że zapewne można
z niej bezpiecznie korzystać, gdyż kod
był pisany tak, aby -ffast-math mu nie
zaszkodziło.
Włączanie jej globalnie dla wszyst-
kich budowanych pakietów to wielki
błąd, np. Perl, którego kiedyś skompilo-
wałem z -ffast-math , nie umiał przejść
przez zestaw testowy weryfikujący
poprawność kompilacji. Błędnie wyko-
nywał niektóre operacje arytmetyczne.
Tą flagą łatwo jest wywołać błędy trudne
do wykrycia.
pomiędzy modelem procesora a warto-
ścią -march .
Jeśli nie widzisz na liście swojego
procesora, to wybierz ten, który jest mu
technologicznie najbliższy, np. dla pierw-
szych Celeronów będzie to pentium2 ,
a dla AMD K5 będzie to i586 . Proceso-
ry AMD Duron to w zasadzie zwykłe
Athlony, tyle że z okrojoną pamięcią
podręczną procesora (relacja między
Duronem a Athlonem jest podobna do tej
między Celeronem a Pentium), co znaczy,
że obowiązuje je zwykle -march=athlon .
Szczegółowe informacje o procesorze
zainstalowanym w danej maszynie znaj-
dują się w pliku /proc/cpuinfo .
Najczęściej używa się flagi -O2 , włącza-
jącej najpopularniejszy zestaw optyma-
lizacji. Opcja -O3 jest specjalna – po jej
włączeniu GCC używa prawie wszystkich
dostępnych trików, aby uzyskać szybszy
kod binarny, ale natura optymalizacji
wywoływanych przez -O3 jest trudna do
przewidzenia i zmienia się w zależności
od konkretnego kompilowanego kodu.
Zwykle -O3 spowoduje rozrośnięcie się
wynikowego kompilatu (skompilowany
program będzie zużywał więcej pamięci
operacyjnej), tylko czasem daje zauwa-
żalne przyspieszenie, a czasem może
nawet doprowadzić do spowolnienia
kodu. Z tego powodu to słabsze -O2 jest
najpopularniejszą flagą, bo, w przeci-
wieństwie do -O3 , oferuje rozsądny (oraz
przewidywalny) kompromis pomiędzy
wydajnością a rozmiarem kodu.
Bardzo interesująca jest opcja -Os .
Jest to specjalny wariant flagi -O2 , zorien-
towany na generowanie mniejszego kodu
wykonywalnego. Włącza on prawie
wszystkie optymalizacje -O2 i dodatkowo
używa kilku technik, aby zmniejszyć
wynikowy kompilat. Flaga -Os pozwala
zmniejszyć zapotrzebowanie na pamięć
operacyjną przy zachowaniu wydajności
bardzo zbliżonej do -O2 .
Najrozsądniejszą opcją uniwersalną
jest -Os lub -O2 . Z -O3 korzystajmy tylko,
gdy mamy absolutną pewność, że da
to wymierny zysk (np. autor kodu tak
twierdzi albo samemu to sprawdziliśmy).
Wybór siły optymalizacji
GCC oferuje przytłaczająco wielką paletę
flag, z których możesz skorzystać. Aby
ułatwić ich stosowanie, powstały specjal-
ne zbiorcze opcje -Ox . Do wyboru mamy
-O0 , -O1 , -O2 , -O3 i -Os . Użycie którejś
z flag -O powoduje włączenie określone-
go zestawu innych flag. Jak widać, flagi
-O są numerowane od 0 do 3 (pomijając
-Os ) i te cyfry generalnie odpowiadają
sile optymalizacji. Im wyższa liczba, tym
mocniej GCC stara się zoptymalizować
kod (optymalizacje powodują też wydłu-
żenie procesu kompilacji). Opcja -O0
wyłącza wszelką optymalizację i raczej
się jej nie używa. Opcja -O1 włącza pod-
stawowe procedury przyspieszania kodu,
ale również rzadko widuje się ją w akcji.
-mfpmath=sse
To flaga przeznaczona dla procesorów
z zestawem instrukcji SSE (przede
wszystkim Pentium3, AMD Athlon lub
nowsze – w razie wątpliwości zajrzyj do
/proc/cpuinfo i sprawdź, czy sse figuruje
w linii lags: ). Dzięki tej fladze kod
będzie używał nowocześniejszych
i szybszych instrukcji SSE, zamiast
odwoływania się do antycznego już
www.lpmagazine.org
51
439034444.001.png 439034444.002.png 439034444.003.png 439034444.004.png 439034444.005.png
 
kompilacja programów
zmiennoprzecinkowego koprocesora
matematycznego i387 (wbudowanego
w każdy współczesny procesor x86).
Flaga ta może dać naprawdę solidny
przyrost wydajności w aplikacjach
zajmujących się obliczeniami matema-
tycznymi (np. programach do rende-
rowania grafiki 3D), więc jeśli masz
procesor obsługujący SSE, to nie zapom-
nij o niej.
Istnieje jeszcze specjalna wariacja tej
flagi: -mfpmath=sse,387 . Sprawia ona, że
GCC generuje kod używający równocze-
śnie klasycznego koprocesora 387, jak
również nowej jednostki SSE. Teoretycz-
nie powoduje to dalsze przyspieszenie,
ale może zakłócić stabilność.
Posiadacze pierwszych Athlonów
(używających -march=athlon lub -mar-
ch=athlon-tbird ) będą zapewne musieli
dodać jeszcze flagę -msse , gdyż dla tych
procesorów GCC nie włącza domyślnie
obsługi SSE (nie posiadały one jeszcze
pełnego zestawu instrukcji SSE).
-s
GCC, podczas normalnej kompilacji,
umieszcza w binarnych plikach wyni-
kowych dużo informacji dotyczących
nazw funkcji i zmiennych użytych
w kodzie źródłowym. Informacje te nie
są wykorzystywane przy normalnym
uruchamianiu binarki, ale pozwalają
użyć tzw. debuggera , czyli programu
do śledzenia wykonywania się kodu.
Pozwala to programistom wychwy-
tywać błędy w kodzie źródłowym
poprzez obserwowanie zawartości
zmiennych w trakcie wykonywania
się programu. Zwykły użytkownik, jak
łatwo się domyślić, nie analizuje kodu
w debuggerach. Dlatego istnieje flaga
-s , która wyłącza wsparcie dla debug-
gera. Nie przyspieszy to działania
kompilatu, ale skróci czas potrzebny na
kompilację i znacząco zmniejszy wielkość
plików produkowanych przez kompi-
lator.
S
-O2 -fomit-frame-pointer -s -pipe"
CXXFLAGS="-march=athlon-xp
S
-O2 -fno-rtti -fno-exceptions -s -pipe"
Istnieje jeszcze zmienna CPPFLAGS. Nie-
którzy omyłkowo biorą ją za optymalizację
C++, ale to nieprawda. Owo CPP oznacza
bowiem C PreProcessor i nie ma nic wspól-
nego z optymalizacjami. Flagi dla C++ usta-
wia się poprzez zmienną CXXFLAGS.
Instrukcje MMX, SSE itp.
Czasem spotyka się zestawy flag typu
-march=athlon-xp -msse -mmmx -m3dnow .
Zwykle jest to zbędne dublowanie flag,
bo odpowiednie dobranie wartości dla
-march powinno automatycznie włączyć
wszystkie dodatkowe zestawy instrukcji.
Przykładowo, włączanie każdego pro-
cesora od Pentium2 w górę, włącza też
zestaw instrukcji MMX. Można się o tym
przekonać używając takich poleceń:
Przekazywanie lag do GCC
Do przekazywania flag kompilatorowi
zwykło używać się zmiennych środo-
wiskowych. Najważniejsze z nich to
CFLAGS i CXXFLAGS. Zmienna CFLAGS
odpowiada za flagi używane przy kom-
pilacji programów napisanych w C,
a CXXFLAGS steruje optymalizacją pro-
gramów C++. Zmienne te są odczytywa-
ne przez większość skryptów ./configure ,
więc po zdefiniowaniu globalnej zmien-
nej CFLAGS, kompilowane aplikacje
powinny zacząć używać podanych tam
optymalizacji.
Przykładowo, aby zdefiniować roz-
sądne flagi dla Athlona XP, można do
swojego ~/.bashrc dodać następujące
wiersze:
-fomit-frame-pointer
Ta flaga optymalizuje niektóre funkcje
w kodzie źródłowym sprawiając, że
ich wywoływanie odbywa się szybciej.
Można spodziewać się po jej użyciu
przyrostu prędkości wykonywania kodu,
spadku jego wielkości oraz mniejsze-
go zużycia pamięci. Współpracuje ona
jednak tylko z kodem C, a jej użycie dla
kodu C++ ma prawie zawsze opłakane
skutki (większy niż normalnie, błędnie
działający kod).
touch pusty.c
gcc -S -v -Q pusty.c
GCC wyświetli m.in. fragment options
enabled: zawierający flagi, których kom-
pilator ma zamiar użyć. Jeśli teraz spróbu-
jesz wykonać:
gcc -S -v -Q pusty.c -march=athlon-xp
zobaczysz, że GCC po włączeniu -march
=athlon-xp automatycznie włączy -mmmx
-m3dnow -msse . Oczywiście, podawanie
tych opcji na siłę niczego nie popsuje, ale
warto pamiętać, że przy dobrze ustawio-
nym -march dodatki typu -m3dnow rzadko
kiedy są potrzebne.
Optymalizacje kompilatora są tematem
bardzo złożonym. Jak pokazuje ten arty-
kuł, można szybko stworzyć sobie całkiem
dobry i wydajny zestaw flag, jeśli zwróci
się uwagę na kluczowe elementy.
-fno-exceptions -fno-rtti
Te dwie flagi odnoszą się tylko do kodu
C++. Powodują wyłączenie pewnych
mechanizmów kompilatora, sprawiając,
że wynikowy kod binarny jest mniejszy
i szybszy. Niektóre programy C++ nie
dadzą się jednak z nią skompilować.
Najlepiej jest spróbować użyć tych flag
i ewentualnie wyłączyć je, jeśli kompila-
cja się nie powiedzie.
export CFLAGS="-march=athlon-xp -O2
S
S
-O2 -fno-rtti -fno-exceptions -s -pipe"
W Internecie:
Już w tym przykładzie widać, dlaczego
istnieją dwie osobne zmienne dla opty-
malizacji C/C++. C++ nie powinno nigdy
otrzymywać flagi -fomit-frame-pointer ,
a kod C zignorowałby flagi -fno-rtti
-fno-exceptions . Z tego powodu zwykle
flagi dla C nie są identyczne z tymi dla
C++. W dystrybucji Gentoo flagi należy
zdefiniować w pliku /etc/make.conf ,
w prawie identycznej postaci (pomijając
jedynie słowo export ):
-pipe
GCC, kompilując jakiś plik, zapisuje
sobie pewne dane w plikach tymczaso-
wych. Po użyciu -pipe przestanie używać
plików tymczasowych, a zamiast nich
posłuży się rurkami (potokami). Nie ma
to wpływu na wynikowy kod, więc nie
jest to tak naprawdę flaga sterująca opty-
malizacją, ale może nieco przyspieszyć
samą kompilację.
• Skrypt samodzielnie generujący
zestaw lag na podstawie /proc/
cpuinfo:
http://www.iol.ie/~padraiga/scripts/
gcccpuopt
• Dokładna lista numerów Family/
Model/Stepping dla różnych proceso-
rów:
http://www.paradicesoftware.com/
specs/cpuid/index.htm
52 sierpień 2004
CFLAGS="-march=athlon-xp
-fomit-frame-pointer -s -pipe"
export CXXFLAGS="-march=athlon-xp
439034444.006.png 439034444.007.png 439034444.008.png
 
Zgłoś jeśli naruszono regulamin