Date: Mon, 20 Nov 2000 19:20:30 +0100 Subject: Profiling, Linker-Mapdateien und obskure gcc-Optionen unter Linux Hi Leute, Stevie meinte, ich soll mal kurz rummailen, wie man unter Linux Profiling betreibt. Profiling ist eine essentielle Methode, um ein Gefühl für die Laufzeit seines Codes zu bekommen, und es wird uns (Convergence) dabei helfen, unsere Programme kleiner und schneller zu machen. Das Programm, daß ich zum Testen profilen möchte, ist dieses: int foo() { } int bar() { int k; for (k=0; k<100000; k++) ; } main(int argc,char *argv[]) { int I=atoi(argv[1]); int J=atoi(argv[2]); int i; for (i=0; i 0.00 0.33 0.00 0.00 0.33 0.00 Letext 0.00 0.33 0.00 __do_global_ctors_aux 0.00 0.33 0.00 __do_global_dtors_aux 0.00 0.33 0.00 __gmon_start__ 0.00 0.33 0.00 _start 0.00 0.33 0.00 call_gmon_start 0.00 0.33 0.00 data_start 0.00 0.33 0.00 fini_dummy 0.00 0.33 0.00 frame_dummy 0.00 0.33 0.00 init_dummy 0.00 0.33 0.00 init_dummy raus. _ctors sind Contructors, _dtors sind Destructors. Das ist ein C++-Konzept, welches aber auch in C für Shared Libraries eine Rolle spielt. Ein anderes wichtiges Tool für uns ist das Linker Mapfile. Es wird so erzeugt: $ gcc -o t t.c -Wl,-Map,foo $ less foo Im Mapfile kann man sehe, welche Objektdateien tatsächlich gelinkt wurden und wegen welchen Symbolen. Das ist bei diesem Trivial-Beispiel nicht sonderlich spannend, aber wenn man z.B. noch dietlibc.a dazu linkt, dann sieht man schon mehr. Wenn man gcc beim kompilieren zusehen will, kann man folgende minder dokumentierte Optionen benutzen: $ gcc -t -o t t.c /usr/i686-pc-linux-gnu/bin/ld: mode elf_i386 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i686-pc-linux-gnu/2.95.2/crtbegin.o /tmp/ccx3gMIt.o /lib/libc.so.6 /usr/lib/gcc-lib/i686-pc-linux-gnu/2.95.2/crtend.o /usr/lib/crtn.o $ gcc -Q -o t t.c foo bar main time in parse: 0.000000 time in integration: 0.000000 time in jump: 0.000000 time in cse: 0.000000 time in gcse: 0.000000 time in loop: 0.000000 time in cse2: 0.000000 time in branch-prob: 0.000000 time in flow: 0.000000 time in combine: 0.000000 time in regmove: 0.000000 time in sched: 0.000000 time in local-alloc: 0.000000 time in global-alloc: 0.000000 time in flow2: 0.000000 time in sched2: 0.000000 time in shorten-branch: 0.000000 time in stack-reg: 0.000000 time in final: 0.000000 time in varconst: 0.000000 time in symout: 0.010000 time in dump: 0.000000 $ Der Witz bei -Q ist, daß gcc die Funktionsnamen ausgibt, wenn er die Funktion gerade kompiliert, d.h. man sieht ungefähr, wie weit er ist, wenn man ihm eine Riesen-Datei gegeben hat. Und jetzt noch ein paar generelle gcc-Tips: $ gcc -pipe -o t t.c sollte man immer benutzen, weil es keine Temp-Dateien anlegt, d.h. das System muß weniger Kram auf Platte schreiben (auch wenn das asynchron geschieht). Spart auch Lebensdauer bei Platten und geht geringfügig schneller auf SMP-Rechnern, weil der Assembler auf der 2. CPU läuft. $ gcc -Os erzeugt optimierten Code, wobei nach Größe optimiert wird. -Os ist wie -O2, läßt aber die Optimierungen weg, die Geschwindigkeit durch Größe erkaufen. -Os kann manchmal kaputten Code erzeugen, d.h. wenn euer Code mysteriös abraucht, dann kann das an -Os liegen. In dem Fall versucht -O2, aber ich habe bei mir auf baileys die glibc, X, meine Shell, meinen Editor und auch sonst fast alles mit -Os kompiliert und es läuft ohne Probleme. $ gcc -malign-double sagt gcc, daß Floats und Doubles anders aligned werden sollen. Das kann bei Raytracern und mp3-Encodern einen substantiellen Geschwindigkeitszuwachs bringen, aber der Code wird inkompatibel zum ABI, d.h. externe Libraries müssen auch mit -malign-double kompiliert werden. Spielt für unsere Set-Top-Boxen keine größere Rolle, denke ich mal, aber ich wollt's trotzdem gesagt haben. $ gcc -fschedule-insns2 Diese Option beeinflußt den Compiler-Scheduler (der Teil, der für die Reihenfolge der Instruktionen zuständig ist). Auf x86-CPUs ist diese Beeinflussung mit einem teilweise deutlichen Geschwindigkeitsgewinn (5-10%) verbunden. Da die Option nicht sonderlich verbreitet ist, ist sie aber auch nicht so gut getestet und kann auch schon mal zu internal compiler errors und kaputtem Code führen, besonders bei gcc-Snapshots (falls jemand sowas einsetzt, bei Redhat 7 ist der Default-gcc ein Snapshot). $ gcc -fomit-frame-pointer Diese Option sagt gcc, daß er keinen Frame Pointer benutzen soll. Der Frame Pointer ist ein Register, das den Stack adressiert. Es gibt einen Stack Pointer und einen Frame Pointer. Der Stack Pointer zeigt aufs Ende des Stacks. Der Frame Pointer gleicht dem Stack Pointer, außer man hat lokale Variablen definiert. Nun weiß der Compiler, wie viel Platz lokale Variablen so belegen (nur alloca wird etwas schwierig), d.h. der Frame Pointer ist nicht direkt essentiell und kann auch für andere Aufgaben benutzt werden. Der Haupteffekt vom Weglassen des Frame Pointers ist, daß der Code etwas kompakter wird. Deutlich schneller wird er eigentlich nicht. Und man kann den Code nicht mehr debuggen. Für Release-Versionen unserer Software ist diese Option durchaus angebracht. Felix