Fast, faster, ICC on Gentoo

Gentoo Linux.

Stundenlanges kompilieren und entsprechende Kenntnisse erfordert die quellbasierte Metadistribution, dafür wird man mit einem skalierbarem, hochkonfigurierbarem System belohnt. Und genau weil man bei Gentoo (in der Regel) alle seine Pakete vor der Installation übersetzen muss, eignet sich diese Distribution perfekt für Performance-Experimente mit Compilern, Compilerflags, USE-Flags und sonstige Spielereien dieser Art.

Der ICC. Eine Alternative.

Ich verwende prinzipiell zwar gerne FOSS (Free/Libre Open Source Software), allerdings lässt sich das in der Praxis sowieso nicht immer gänzlich durchführen. Hier habe ich einmal über den Tellerrand geschaut und (dank' eines kleinen Tipps) mir den ICC angesehen. Der ICC (Intel's C/C++ Compiler) ist ein closed-source Compiler, welcher einer privaten Nutzung mit einer nicht komerziellen Lizenz zur Verfügung steht.
Aber warum sollte ich mir eine umständliche Installation eines closed-source Compilers antun, wenn ich doch mein gesamtes Gentoo-System mit dem frei zur Verfügung stehenden GCC übersetzen kann?
Ja, ganz einfach: weil ich diesen Artikel gelesen habe und ihn danach einfach ausprobieren musste.

Die Installation.

Zunächst erstellt man sich einen Account auf der Intel-Seite des Compilers. Dort erhält man dann eine non-commercial Lizenz. Die entsprechende .lic-Datei, welche einem dann per E-Mail zugesendet werden soll (man kann sich im Account-Menü auch durchklicken, bis man auf die Lizenzdatei stößt), speichert man nach /opt/intel/licenses/license.lic ab. Danach wird es einfach:

$ emerge -v dev-lang/icc

Sollte alles funktioniert haben, startet der Download und die Installation. Die Download-Größe ist zwar nicht ohne, aber bei heutiger DSL-Flatrate kein Thema mehr.

Erste Schritte.

Möchte man eigene C/C++ Programme mit dem icc übersetzen, unterscheiden sich die grundlegenden Parameter nicht sonderlich von denen des gcc. Ich habe zwar nur einige wenige getestet, aber selbst sowas wie ein
 
$ icc stable.cpp -o stable.out -O2 -w `pkg-config --cflags opencv --libs opencv`

läuft problemlos. Ansonsten hilft einem man icc sicherlich weiter.

Erste Performance-Tests.

Zum Performance-Vergleich verwendete ich dann ein kleines von mir geschriebenes C-Programm mit welchem ich ein Zufallszahlen-Array von 2^16 Zahlen mittels Selection-Sort-Algorithmus sortiert habe.

Ich kompilierte den Code mit gcc programm.c -o programm_gcc.out und icc programm.c programm_icc.out zum vergleichen, da Optimierungen wie -O2 oder -unroll-aggressive sowieso eine Laufzeit von 0,000000001 Sekunden (+/- ein paar Nullen) erbringen. Lohnen sich für diesen Testfall also leider nicht.

GCC:

Messung 1: 6.060762
Messung 2: 6.056119
Messung 3: 6.032691
Messung 4: 6.071855

ICC:

Messung 1: 1.328401
Messung 2: 1.314523
Messung 3: 1.314764
Messung 4: 1.316440

Ergibt also eine durchschnittliche Laufzeit von  6,05535675 Sekunden für den GCC und 1,318532 Sekunden für den ICC.

ICC im Gentoo-System.

Trotz der tollen Laufzeiten, die sich meiner Messung nach und z.B. dem Artikel von Kay Königsmann ergeben, sollte man dennoch nicht den Versuch starten und gleich das ganze System mit dem ICC übersetzen. Denn leider enthalten wichtige Komponenten, wie Kernel, bash, oder sys-libs/glibc gcc-spezifischen Code, sodass eine Übersetzung im Moment nicht möglich ist[1].
Aber man kann trotzdem viele Komponenten seines Gentoo-Systems mit dem ICC übersetzen. Es ist sinnvoll, hierzu entsprechende Einträge in der /etc/portage/make.conf zu machen:
ICCCFLAGS="-O2 -xS -ipo -ip -w -fast -unroll-aggressive -no-prev-div -gcc"
ICCCXXFLAGS="${ICCCFLAGS}
wären das z.B. bei meiner Maschine. Aber hierzu gibt es auch einiges in diversen Wiki's nachzulesen[1] [2].
Anschließend kann man sich noch ein /etc/portage/bashrc anlegen:
[ -r ${ROOT}/etc/portage/package.icc ] || return 0
while read -a target; do
  if [ "${target}" = "${CATEGORY}/${PN}" ]; then
    export OCC="icc"
    export OCXX="icpc"
    export CFLAGS=${ICCCFLAGS}
    export CXXFLAGS=${ICCCXXFLAGS}
    if [ -r ${ROOT}/etc/portage/package.icc-cflags ]; then
      while read target flags; do
        if [ "${target}" = "${CATEGORY}/${PN}" ]; then
          export CFLAGS="$flags"
          export CXXFLAGS="$CFLAGS"
          break
        fi
      done < ${ROOT}/etc/portage/package.icc-cflags
    fi
break
fi
done < ${ROOT}/etc/portage/package.icc

if [ -r ${ROOT}/etc/portage/package.gcc-cflags ]; then
while read target flags; do
if [ "${target}" = "${CATEGORY}/${PN}" ]; then
export CFLAGS="$flags"
export CXXFLAGS="$CFLAGS"
break
fi
done < ${ROOT}/etc/portage/package.gcc-cflags
fi

if [ "${OCC}" != "" ]; then
export CC_FOR_BUILD="${OCC}" #workaround gcc detection function in toolchain-funcs.eclass
fi
Damit werden jetzt einfach alle Pakete, welche sich in der /etc/portage/package.icc befinden, mit dem icc kompiliert, der Rest mittels gcc. Auch ist es möglich, für jedes Paket spezifische icc-cflags anzulegen, dieses dann in der /etc/portage/package.icc-cflags.

Die Paketangaben in den Dateien erfolgen ähnlich, wie in der package.mask oder in der package.use.
Hier bei mir funktionierende package.icc. (Stand: 03.Nov.2013)

Fazit.

In manchen Fällen lohnt sich tatsächlich ein Blick über den Tellerrand hinaus. So können 25-30% mehr Performance z.B. die Kaufentscheidung neuer Hardware beeinflussen.
Die meisten Geschwindigkeit vieler Programme wird eben nicht nur durch die Laufzeitklassen der Algorithmen und der zugrunde liegenden Hardware bestimmt, sondern auch, wie der Compiler den Programm-Code übersetzt. Und vielleicht noch ein paar andere Faktoren, wie Betriebssystem, etc...

Schreibe einen Kommentar