Boost.Xpressive: Compile-Dauer und Dateigrößen
-
Tag zusammen,
Einige hier im Forum scheinen ja große Fans von Boost.Xpressive zu sein. Von der Idee her gefällt mir Xpressive auch sehr gut, was mich aber abschreckt sind die langen Compile-Zeiten (mein Rechner: Core 2 Duo, 1.5 GHz, 3 GB RAM) und die Größe der resultierenden Binaries.
Nehmen wir mal das Hello-World Beispiel. Wenn ich das mit gcc 4.3.2 ohne besondere Compiler-Optionen kompiliere, dann dauert das 12 Sekunden, und das Binary ist ziemlich genau 1 MB (!) groß.
Mit "-s -O2 -DNDEBUG" reduziert sich die Größe des Binaries zwar auf 200 kB (immerhin), dafür ist der Compiler dann aber auch schon 23 Sekunden beschäftigt. Und wie gesagt, das ist nur Hello-World. Wie soll das erst werden, wenn ich ernsthaft Sachen damit erledigen will?Gibt es irgendwelche Tricks, wie ich die Compile-Zeiten und insbesondere auch die Binaries halbwegs klein halten kann? Vielleicht bestimmte Compiler-Einstellungen, oder auch Konstrukte in Xpressive, die man eher meiden sollte?
Ein neuer Rechner oder ein anderer Compiler sind leider beides keine Optionen...
-
naja im normal fall wird in ner entwicklungsumgebung nur die veränderte datei neu compiled, so wird die compile zeit in grenzen gehalten
aber naja ansonsten kann man nicht viel machen
-
Benutz einfach Precompiled Header.
-
fwefewf schrieb:
Benutz einfach Precompiled Header.
Das ist eine gute Idee, werde ich auf jeden Fall ausprobieren. An den teilweise riesigen Binaries ändert das aber natürlich auch nichts...
-
Also zwei Dinge wurden schon genannt:
1. Es wird nicht immer das ganze Projekt kompiliert, wenn man eine anständige Entwicklungsumgebung oder Werkzeuge hat.
2. Precompiled Header ist sicher ein gutes Stichwort
3. Am meisten Zeit braucht es, um die Header einfach nur zu parsen. Xpressive besteht fast nur oder sogar vollkommen nur aus Header. Da ist also nichts in einer Bibliothek vorkompiliert. Die Konstrukte, welche man dann daraus baut, werden deutlich schneller zusammengewürfelt, verschlingen im Vergleich also oft deutlich weniger Compilezeit.
Also auch grosse Projekte mit Xpressive brauchen für die Xpressive Konstrukte nicht viel mehr Zeit als kleinere Projekte, im Vergleich zu den Anzahl Zeilen Xpressive und dem parsen der Header.
4. Die grösse der ausführbaren Datei ist heutzutage echt nicht mehr so wichtig. Desweiteren, wenn man optimiert und somit eine release Version erstellt, dann hast du selber gesehen, dass die Dinger kleiner werden.Grüssli
-
Dravere schrieb:
1. Es wird nicht immer das ganze Projekt kompiliert, wenn man eine anständige Entwicklungsumgebung oder Werkzeuge hat.
Natürlich. Aber wenn mein Parser, der vom Funktionsumfang her nur einen Bruchteil des Projekts ausmacht (in Codezeilen gemessen etwa 5%), länger zum kompilieren braucht als der ganze Rest, dann läuft da doch wohl irgendwie was falsch...
Dravere schrieb:
4. Die grösse der ausführbaren Datei ist heutzutage echt nicht mehr so wichtig.
Da hast du sicher nicht Unrecht, aber das ist ja noch kein plausibler Grund, unnötig Ressourcen zu verschwenden. Ich erwarte ja nicht, daß eine moderne, komfortable C++-Library genauso schlanke Binaries erzeugt wie handgeschrieber Code. Aber irgendwo sollten Aufwand/Nutzen ja noch in einer vernünftigen Relation stehen.
Vorkompilierte Header habe ich gerade ausprobiert, einen gewissen Unterschied macht das schon. Leider allerdings nicht ganz so viel wie ich erhofft hatte, der Zeitgewinn liegt so bei etwa 20-30%.
-
dooooomi schrieb:
Natürlich. Aber wenn mein Parser, der vom Funktionsumfang her nur einen Bruchteil des Projekts ausmacht (in Codezeilen gemessen etwa 5%), länger zum kompilieren braucht als der ganze Rest, dann läuft da doch wohl irgendwie was falsch...
dooooomi schrieb:
Da hast du sicher nicht Unrecht, aber das ist ja noch kein plausibler Grund, unnötig Ressourcen zu verschwenden.
Du scheinst wohl die Idee hinter Xpressive (wie übrigens z.B. auch Spirit) nicht zu verstehen.
Die Idee ist nämlich, dass man Code erzeugen kann, welcher vom Kompiler und Linker völlig optimiert werden kann und zwar in erster Linie durch "Inlining". Also das direkte aufheben von Funktionen und einfügen des Codes, statt des Aufrufes der Funktion.
Das führt nicht nur zu sehr viel schnellerem Code, sondern zu einer Menge zusätzlicher Arbeit für den Kompiler und Linker. Desweiteren entstehen natürlich viel grössere Binaries, weil der Code eben nicht in Funktionen ausgelagert bleibt, sondern vervielfacht wird, damit die Funktionsaufrufe wegfallen.Deshalb ist es auch keine Verschwendung von Ressourcen, sondern eine bewusste Anwendung dieser Ressourcen. Es ist eigentlich alles so gewollt, man will Speicher verbrauchen, um mehr Geschwindigkeit zu bekommen. Das der Kompiler und Linker dafür länger braucht, ist eine logische Sache, da kann man halt nicht viel mehr tun, als zu hoffen, dass die Entwickler des Kompilers auch solche für Geschwindigkeit optimierten Bibliotheken einsetzen
Grüssli
-
Dravere schrieb:
Du scheinst wohl die Idee hinter Xpressive (wie übrigens z.B. auch Spirit) nicht zu verstehen.
Die Idee ist nämlich, dass man Code erzeugen kann, welcher vom Kompiler und Linker völlig optimiert werden kann und zwar in erster Linie durch "Inlining". Also das direkte aufheben von Funktionen und einfügen des Codes, statt des Aufrufes der Funktion.
Das führt nicht nur zu sehr viel schnellerem Code, sondern zu einer Menge zusätzlicher Arbeit für den Kompiler und Linker. Desweiteren entstehen natürlich viel grössere Binaries, weil der Code eben nicht in Funktionen ausgelagert bleibt, sondern vervielfacht wird, damit die Funktionsaufrufe wegfallen.Doch doch, die Idee dahinter habe ich schon verstanden, sie führt nur leider nicht immer zu optimalen Ergebnissen. Du willst mir doch jetzt nicht riesig aufgeblähte Binaries als Feature verkaufen?
Und wo du schon Spirit erwähnst: Zumindest v1 ist nicht gerade für seine überragende Geschwindigkeit bekannt, und war laut Aussage der Entwickler auch nie darauf ausgelegt (v2 ist eine andere Geschichte).Inlining hin oder her, ein großer Teil des Codes ist einfach unnötig und könnte prinzipiell noch weiter wegoptimiert werden. Ich würde mal behaupten, daß bei meinem Xpressive-Parser mindestens die Hälfte des generierten Codes "tot" ist und überhaupt niemals aufgerufen wird.
Zugegebenermaßen muß man das nicht zuletzt auch dem Compiler anlasten, C++ mit heftigem Template-Einsatz ist einfach nicht die Stärke des gcc (was den erzeugten Binärcode angeht). Daß es auch anders geht, beweisen ja einige andere Compiler.Aber meine ursprüngliche Frage zielte eher darauf ab, wie man dem Compiler ein bißchen entgegenkommen kann. Um mal ein Beispiel zu nennen, auf das ich vorhin gestoßen bin:
Der Code hier (wobei foo und bar nur als Teile anderer Ausdrücke vorkommen)
sregex foo = ...; sregex bar = ...; sregex baz = foo >> bar;
produziert deutlich größere Binaries als
BOOST_AUTO(foo, ...); BOOST_AUTO(bar, ...); sregex baz = foo >> bar;
Da gibt es sicherlich gute Gründe für, mit Geschwindigkeit haben die aber nichts zu tun.
-
Ach ja, eine andere Frage hätte ich noch zu Xpressive:
Ich benutze regex_match() um zu testen, ob ein String einem regulären Ausdruck entspricht. Angenommen, der Test schlägt fehl. Gibt es dann eine Möglichkeit herauszufinden, ab welcher Stelle der String nicht mehr paßt? Also quasi die Position des ersten Zeichens, ab dem kein Match mehr möglich ist?
-
dooooomi schrieb:
Doch doch, die Idee dahinter habe ich schon verstanden, sie führt nur leider nicht immer zu optimalen Ergebnissen.
Was verstehst du unter optimalen Ergebnissen? Optimale Ergebnisse für dich oder den Kunden? Und optimale Ergebnisse in welchem Bereich?
dooooomi schrieb:
Du willst mir doch jetzt nicht riesig aufgeblähte Binaries als Feature verkaufen?
Doch, genau das will ich. Geschwindigkeit durch grosse Binaries. Im MSVC hat man genau auch solche Optionen für den Compiler. "Favor fast code" zum Beispiel, dann wird das Binary zwar grösser, dafür läuft der Code schneller. Man hätte auch "Favor small code" auswählen können, nur macht das wohl kaum einer
dooooomi schrieb:
Inlining hin oder her, ein großer Teil des Codes ist einfach unnötig und könnte prinzipiell noch weiter wegoptimiert werden. Ich würde mal behaupten, daß bei meinem Xpressive-Parser mindestens die Hälfte des generierten Codes "tot" ist und überhaupt niemals aufgerufen wird.
Ziemlich sicher nicht in der Release-Version. Ich meine 200Kb sind ja wirklich nicht die Welt. Und dass der Debug Code grösser ist, womöglich auch toten Code drin hat, dass ist ja fast normal und kann man ruhig hinnehmen.
dooooomi schrieb:
... BOOST_AUTO vs. Normal ...
Da gibt es sicherlich gute Gründe für, mit Geschwindigkeit haben die aber nichts zu tun.Wahrscheinlich kommt dies durch die virtuelle Funktion, welche in
sregex
verwendet wird. BOOST_AUTO lässt dich den tatsächlichen Code, ohne virtuelle Funktion speichern. Das ist meistens ein extrem template verschachtleter Typ. Dadurch ist beim Zusammenfügen der beiden Typen der ganze Code eindeutig bekannt. Daher kann auch noch zwischen den Blöcken (foo >> bar
) optimiert werden.
Wenn dusregex
verwendest, dann wird intern eine virtuelle Funktion verwendet, um den komplexen Typ zu speichern. Man kann sich das in etwa so vorstellen:class Foo { private: class BasicCaller { public: virtual void call(...) const = 0; }; template<typename CallableT> class Caller : public BasicCaller { private: CallableT m_callable; public: Caller(CallableT callable) : m_callable(callable) { } public: virtual void call(...) const { // ... rufe das Ding auf! } }; private: Basic* m_basic; public: template<typename CallableT> Foo(CallableT callable) : m_basic(new Caller<CallableT>(callable)) { } };
Natürlich sehr stark vereinfach.
Dadurch hast du aber das Problem, dass die Blöcke beim Kompilieren untereinander nicht bekannt sind. Es kann also zwischen den Blöcken nicht optimiert werden, wodurch grösserer Code entsteht. Zumindest ist das meine Vermutung und so habe ich das verstanden ... möchte mich da jetzt nicht als Experte hinsetzen
Zum regex_match:
Wäre mir aktuell nicht bekannt, dass sowas geht. Könnte mir vorstellen, dass dies teilweise auch recht komplex umzusetzen wäre, wenn die Regex ein wenig komplexer werden. Aber schau am besten mal in der Doku nach oder frag in der Mailing List.Grüssli
-
Dravere schrieb:
Was verstehst du unter optimalen Ergebnissen? Optimale Ergebnisse für dich oder den Kunden? Und optimale Ergebnisse in welchem Bereich?
Optimal wäre für mich Code, der so schnell ist wie möglich, so klein wie möglich, und dabei genau das macht was er soll. Aber ok, daß man in der Praxis in mindestens einem von den drei Punkten Abstriche machen muß, ist mir auch klar.
Dravere schrieb:
dooooomi schrieb:
Du willst mir doch jetzt nicht riesig aufgeblähte Binaries als Feature verkaufen?
Doch, genau das will ich. Geschwindigkeit durch grosse Binaries.
Beim gcc kann man auch wahlweise auf Geschwindigkeit oder auf Code-Größe optimieren (in mehreren Abstufungen). Aber das heißt ja noch nicht, daß größere Binaries zwangsläufig schneller sind. In einigen Fällen kann sogar das Gegenteil der Fall sein, z.B. wenn der Code zu groß wird für den CPU-Cache.
Dravere schrieb:
dooooomi schrieb:
Ich würde mal behaupten, daß bei meinem Xpressive-Parser mindestens die Hälfte des generierten Codes "tot" ist und überhaupt niemals aufgerufen wird.
Ziemlich sicher nicht in der Release-Version. Ich meine 200Kb sind ja wirklich nicht die Welt.
200 kB war Hello-World, mein eigener Code ist deutlich größer. Und natürlich sind 200 kB nicht sooo viel, aber man muß das halt in Relation setzen zu dem, was der Code wirklich macht.
Auch in der Release-Version produziert der gcc sehr viel unnötigen Code. Wie gesagt, das ist im Wesentlichen eine Schwäche des Compilers. Bei Xpressive habe ich da jetzt noch keine konkreten Erfahrungswerte, aber z.B. bei Boost.Python produziert gcc Binaries, die im Schnitt 3x so groß sind wie mit MSVC, ohne daß sie in irgendeiner Weise schneller oder sonstwie besser wären.
-
Dann wechsel doch von gcc nach msvc. :p
-
Bulli schrieb:
Dann wechsel doch von gcc nach msvc. :p
Wenn du mir den MSVC nach *nix portierst und unter einer freien Lizenz verfügbar machst, dann werde ich darüber nachdenken
-
Wieso denn so klein wie möglich? Was ist der Vorteil davon? Klar sollte man nicht gleich alles zu müllen, aber in erster Linie sollte man doch schon eher auf Geschwindigkeit achten.
Natürlich ist da der GCC auch nicht wirklich so optimal. Bei mir erzeugt g++ bei folgendem Code mit voller Optimierung eine fast 500Kb grosse Datei:#include <iostream> int main() { std::cout << "Hello World!" << std::endl; return 0; }
Die überdimensionierte Grösse kommt also nicht von Xpressive, sondern ist einfach der GCC, welcher da zu wenig optimiert. Aber das ist ja schon lange bekannt.
Wenn man wirklich eine kleinere Binary möchte, dann sollte man auf die Standardbibliothek verzichten und gleich mit den Systemfunktionen arbeiten, dann kann man deutlich kleinere Binaries erreichen.Und das der Code das macht, was er soll, das ist natürlich klar und liegt meistens in den Händen der Programmierer
Der MSVC kann man übrigens als freier Kompiler bekommen, nämlich in der Express Edition. Einzig die Portierung auf ein UNIX System dürfte schwierig werden
Grüssli
-
Dravere schrieb:
Bei mir erzeugt g++ bei folgendem Code mit voller Optimierung eine fast 500Kb grosse Datei:
...Moment mal, 500kB?! Ich weiß nicht, was dein Compiler mit all dem Speicher macht. Linkst du die Standard-Library statisch, oder sowas? Bei mir (gcc unter Linux) kommt da jedenfalls ein Binary von 4056 Bytes bei raus.
Dravere schrieb:
Der MSVC kann man übrigens als freier Kompiler bekommen, nämlich in der Express Edition.
Was du meinst ist kostenlos
-
dooooomi schrieb:
Moment mal, 500kB?! Ich weiß nicht, was dein Compiler mit all dem Speicher macht. Linkst du die Standard-Library statisch, oder sowas? Bei mir (gcc unter Linux) kommt da jedenfalls ein Binary von 4056 Bytes bei raus.
Frag mich nicht. Unter WinXP, g++ von MinGW. Kenne mich zu wenig aus um zu wissen, ob der nun statisch oder shared gelinkt hat, aber dachte eigentlich shared. Aber bisher war meine Erfahrungen vollkommen in diese Richtung mit dem g++. Riesen Dinger kommen dabei raus, auch wenn man optimiert.
dooooomi schrieb:
Was du meinst ist kostenlos
Und du meinst wohl Open Source
Denn eine freie Lizenz bedeutet für mich, dass man das Ding anwenden kann ohne Lizenzgebühren zu zahlen. Und das ist der Fall bei der Express Edition. Bei einem Open Source Projekt muss das aber nicht der Fall sein. Du hast dann vielleicht freien Code, aber nicht eine freie Lizenz.
Grüssli
-
Dravere schrieb:
dooooomi schrieb:
Was du meinst ist kostenlos
Und du meinst wohl Open Source
Nö, ich meine Freie Software
Dravere schrieb:
Denn eine freie Lizenz bedeutet für mich, dass man das Ding anwenden kann ohne Lizenzgebühren zu zahlen. Und das ist der Fall bei der Express Edition.
Das ist aber nicht das, was der Begriff "freie Lizenz" meint.
Im Englischen ist "free" leider wirklich mehrdeutig (siehe "Freeware" vs. "Free Software"). Die FSF weicht aus dem Grund sogar ganz gerne auf Spanisch aus und redet von "Software Libre". Aber auch im Deutschen kann man sehr wohl zwischen "frei" und "kostenlos" unterscheiden.Dravere schrieb:
Bei einem Open Source Projekt muss das aber nicht der Fall sein. Du hast dann vielleicht freien Code, aber nicht eine freie Lizenz.
Eben, deswegen ist Open Source ja auch nicht gleich Freie Software.
Aber ich glaube, das wird hier jetzt ein bißchen zu Off-Topic
-
Ich meine mal gehört zu haben, dass der G++ die Debug-Symbole auch im Release-Modus mit reinpackt
Ich schaue mal, ob ich den Thread noch finde, da ging's auch um Binary-Größen, G++ vs MSCV.
-
Probier mal das Programm strip aus, das müsste beim gcc beiliegen (beim mingw ist es dabei).
-
mammamia;