wtorek, 26 maja 2009

Precompiled header

Budowanie mojego projektu przekroczyło 150.000 ms. Postanowiłem, więc skorzystać z tej techniki, aby nieco skrócić czas oczekiwania.
Zysk jest całkiem spory, różnica 36 sekund, czyli około 20 %.

A teraz jak uzyskać pre kompilowane nagłówki pod Eclipsem ?
Eclispe z CDT nie posiada narzędzi do obsługi tej techniki, więc zapomnijcie o magicznym przełączniku. Wszystko trzeba zrobić samemu.
Na szczęście GCC automatycznie wykorzystuje pch, o ile one istnieją. Sami budujemy nagłówek i umieszczamy pch w odpowiednim miejscu, a kompilator go znajdzie i wykorzysta.

Jak zrobić nagłówek?
Tworzymy zwykły plik .h i włączamy w nim wszystkie potrzebne pliki nagłówkowe.
Przykład:
#ifndef STD_H_
#define STD_H_

#include <string>
#include <fstream>
#include <sstream>
#include <map>
#include <deque>
#include <vector>

#endif /* STD_H_ */


Jak zbudować nagłówek ?
Do zbudowania nagłówka wywołujemy z linii komend "g++ -opcje -xc++-header -otest.h.gch test.h". W efekcie powinniśmy otrzymać plik test.h.gch.
- test.h to rzecz jasna nazwa pliku
- teoretycznie flaga -x nie jest potrzebna, bo gcc zna rozszerzenie .h, ale jakoś nie za bardzo bez niej chciało działać
- opcje powinny być takie same jak projektu korzystającego z nagłówka

Jak z nagłówka skorzystać ?
- pch zostanie wykorzystany automatycznie
- gcc wykorzysta ten plik, który znajdzie pierwszy, przy czym najpierw sprawdza czy w danym katalogu jest pch, dopiero potem czy jest zwykły plik. Najprościej oba pliki umieścić w tym samym katalogu
- projekt powinien mieć te same opcje co pch
- pch może być tylko jeden na raz

Co zrobić z błędem "calling fdopen: Bad file descriptor" ?
Pch może zostać użyte tylko raz podczas kompilacji. Jeżeli mamy sytuację w której plik pch zostanie włączony dwa razy, zobaczymy taki właśnie błąd. Przykładowo mamy:
foo.h włącza "std.h"
bar.h włącza "std.h"
foobar.cpp włącza "foo.h" i "bar.h"
Nie wiem jak to rozwiązują inni, ja po prostu opatrzyłem dyrektywę strażnikiem:
//Precompiled header
#ifndef STD_H_
#include "std.h"
#endif


Warto przeczytać: gcc/Precompiled-Headers

6 komentarzy:

Gynvael Coldwind pisze...

Hah, cieekawe. Nie wiedziałem że GCC ma PCH ;> Thx za wpis ;>

Adam Śmigielski pisze...

Wow, pierwszy komentarz. Chyba opatrzę go w ramkę ;)

Pozdrawiam.

Anonimowy pisze...

Nie jest prawidłowe włączanie prekompilowanego headera do plików nagłówkowych.
Powinien on się znaleźć na samym początku plików cpp - eliminuje to problem: "calling fdopen: Bad file descriptor" i jemu podobne.
Szczerze mówiąc sam popełniłem taki błąd.

Adam Śmigielski pisze...

Tylko potem trzeba w pliku .h deklarować:

namespace std {
class string;
class vector;
}

Zaraz, przecież to są template'y. ZARAZA, czy tak w ogóle można zrobić ?? Chyba jednak nie, trzeba teraz poszukać definicji klasy vector i przepisać co do literki. Przepisujemy, kompilujemy i sypią się błędy.

Co z tym fantem zrobić ?

Pytam poważnie, bo generalnie to się z Tobą zgadzam. Im mniejszy header tym lepiej.

raffimoni pisze...

Nie musisz pisać:
namespace std
{
class string;
class vector;
}

Rozważmy sytuację, gdy mamy klasę Foo w plikach foo.h i foo.cpp oraz, że klasa ta korzysta z klasy vector oraz prekompilowany header std.h (zakładamy, że std.h zawiera #include vector):

foo.h:
------
#ifndef FOO_H_
#define FOO_H_

class Foo
{
std::vector a;
}

#endif

foo.cpp:
-------
#include "std.h" //zawsze na początku
#include "foo.h"

Dzięki temu podczas kompilacji (kompilowane są pliki cpp, headery są tylko włączane) przed deklaracją klasy Foo znajdzie się dyrektywa #include vector (vector powinno być w nawiasach <>, ale edytor mi krzyczy)

I jeszcze jedna ważna kwestia, każdy plik cpp powinien się zaczynać od #include "std.h", bo jeżeli nawet ten plik cpp nie korzysta z czegokolwiek z std.h, to może inkludować np. foo.h, która już korzysta.

pzdr
raffimoni, wcześniej anonimowy

Adam Śmigielski pisze...

Chyba kiepski ze mnie blogger, nawet się nie zorientowałem, że ktoś mi zostawił komentarz :)

Rozwiązanie wydaje się rozsądne. Przetestuje i powiem co o tym myślę.