Wann sollte ich C ++ - Ausdrucksvorlagen in der Informatik verwenden und wann sollte ich sie * nicht * verwenden?

24

Angenommen, ich arbeite an einem wissenschaftlichen Code in C ++. In einer kürzlich mit einem Kollegen geführten Diskussion wurde argumentiert, dass Ausdrucksvorlagen eine wirklich schlechte Sache sein könnten, die möglicherweise die Kompilierbarkeit von Software nur auf bestimmten gcc-Versionen ermöglicht. Angeblich hat dieses Problem einige wissenschaftliche Codes betroffen, wie in den Untertiteln dieser Parodie des Untergangs angedeutet . (Dies sind die einzigen mir bekannten Beispiele, daher der Link.)

Andere Leute haben jedoch argumentiert, dass Ausdrucksvorlagen nützlich sind, weil sie, wie in diesem Artikel im SIAM Journal of Scientific Computing , zu Leistungssteigerungen führen können, indem die Speicherung von Zwischenergebnissen in temporären Variablen vermieden wird.

Ich weiß nicht viel über Template-Metaprogrammierung in C ++, aber ich weiß, dass dies ein Ansatz ist, der bei der automatischen Differenzierung und bei der Intervallarithmetik verwendet wird. So bin ich zu einer Diskussion über Ausdrucksvorlagen gekommen. Wann sollte ich C ++ - Ausdrucksvorlagen in der Informatik verwenden und wann sollte ich sie vermeiden, wenn sowohl die potenziellen Vorteile bei der Leistung als auch die potenziellen Nachteile bei der Wartung berücksichtigt werden (wenn dies überhaupt das richtige Wort ist)?

Geoff Oxberry
quelle
Ah, das Video ist zu lustig. Ich wusste nicht, dass es existiert. Wer hat es geschafft, weißt du?
Wolfgang Bangerth
Keine Ahnung; Ein paar der PETSc-Leute haben mir einmal Links geschickt. Ich denke, ein FEniCS-Entwickler hat es geschafft.
Geoff Oxberry
Die Videoverbindung ist unterbrochen und ich sterbe vor Neugier. Neuer Link?
Praxeolitic
Oh, egal, ich sehe, dass YouTube für unsere Hitler-Videos gekommen ist.
Praxeolitic

Antworten:

17

Mein Problem mit Ausdrucksvorlagen ist, dass sie eine sehr undichte Abstraktion sind. Sie schreiben sehr komplizierten Code, um eine einfache Aufgabe mit besserer Syntax zu erledigen. Wenn Sie jedoch den Algorithmus ändern möchten, müssen Sie sich mit dem unsauberen Code herumschlagen, und wenn Sie mit Typen oder Syntax in Konflikt geraten, erhalten Sie völlig unverständliche Fehlermeldungen. Wenn Ihre Anwendung perfekt einer Bibliothek zugeordnet ist, die auf Ausdrucksvorlagen basiert, ist dies möglicherweise eine Überlegung wert. Wenn Sie sich jedoch nicht sicher sind, würde ich empfehlen, nur normalen Code zu schreiben. Sicher, der Code auf hoher Ebene ist weniger hübsch, aber Sie können einfach das tun, was getan werden muss. Dies hat den Vorteil, dass die Kompilierungszeit und die Binärgrößen erheblich verkürzt werden und Sie aufgrund der Auswahl der Compiler- und Kompilierungsflags keine großen Leistungsunterschiede zu bewältigen haben.

Jed Brown
quelle
Ja, ich habe einige der langwierigen Fehlermeldungen aus erster Hand gesehen, als ich Code von gcc 2.95 auf gcc 4.x portieren musste, und der Compiler hat alle möglichen Fehler in Bezug auf Vorlagen ausgegeben. Ein Kollege von mir entwickelt eine Bibliothek mit Vorlagen für die Intervallarithmetik in C ++ (und fügt neue Funktionen hinzu, die nicht in Boost :: Interval enthalten sind, um weitere Nachforschungen anzustellen), und ich möchte nicht, dass der Code zu einem Albtraum wird kompilieren.
Geoff Oxberry
12

Andere haben die Frage kommentiert, wie schwierig es ist, ET-Programme zu schreiben und wie komplex es ist, Fehlermeldungen zu verstehen. Lassen Sie mich das Problem der Compiler kommentieren: Es ist wahr, dass vor einiger Zeit eines der großen Probleme darin bestand, einen Compiler zu finden, der dem C ++ - Standard entspricht, damit alles funktioniert und es portabel funktioniert. In der Folge haben wir viele Fehler gefunden - ich habe 2-300 Fehlerberichte in meinem Namen, die über gcc, Intel icc, IBM xlC und Portland's pgicc verteilt sind. Folglich ist das deal.II-Konfigurationsskript ein Repository für eine große Anzahl von Compiler-Bug-Tests, hauptsächlich im Bereich von Vorlagen, Friend-Deklarationen, Namespaces usw.

Es stellt sich jedoch heraus, dass die Compiler-Macher ihre Sache wirklich gut gemacht haben: Heute bestehen GCC und ICC alle unsere Tests und es ist einfach, Code zu schreiben, der zwischen den beiden portierbar ist. Ich würde sagen, dass die ggA nicht weit hinterherhinkt, aber es gibt eine Reihe von Macken, die im Laufe der Jahre nicht zu verschwinden scheinen. Auf der anderen Seite ist xlC eine ganz andere Geschichte - sie beheben alle 6 Monate einen Fehler, aber trotz jahrelanger Einreichung von Fehlerberichten ist der Fortschritt extrem langsam und xlC konnte deal.II noch nie erfolgreich kompilieren.

Dies alles bedeutet Folgendes: Wenn Sie sich an die beiden großen Compiler halten, können Sie davon ausgehen, dass sie nur heute funktionieren. Da die meisten Computer und Betriebssysteme heutzutage normalerweise mindestens einen haben, reicht das aus. Die einzige Plattform, auf der es schwieriger ist, ist BlueGene, wo der System-Compiler normalerweise xlC ist, mit all seinen Fehlern.

Wolfgang Bangerth
quelle
Haben Sie aus Neugier versucht, gegen die neuen xlc-Compiler auf / Q zu kompilieren?
Aron Ahmadia
Ich gebe zu, dass ich xlC aufgegeben habe.
Wolfgang Bangerth
5

Ich habe vor langer Zeit ein wenig mit ETs experimentiert, als, wie Sie sagten, Compiler noch mit ihnen zu kämpfen hatten. Ich habe die Blitzbibliothek für lineare Algebra in einem Code von mir verwendet. Das Problem war dann, den guten Compiler zu bekommen und da ich kein perfekter C ++ - Programmierer bin, die Compiler-Fehlermeldungen zu interpretieren. Letzteres war einfach unüberschaubar. Der Compiler würde im Durchschnitt etwa 1000 Zeilen mit Fehlermeldungen erzeugen. Auf keinen Fall konnte ich meinen Programmierfehler schnell finden.

Weitere Informationen finden Sie auf der oonumerics- Webseite (es gibt die Abläufe von zwei ET-Workshops).

Aber ich würde weit weg von ihnen bleiben ...

GertVdE
quelle
Die Compiler-Fehlermeldungen sind in der Tat eines meiner Anliegen. Mit einem Teil des C ++ - Templatcodes, den ich kompiliere, um Bibliotheken für meine Projekte zu erstellen, generiert der Compiler möglicherweise Hunderte von Zeilen mit Warnmeldungen. Es ist jedoch nicht mein Code, ich verstehe es nicht und im Allgemeinen funktioniert es, also lasse ich es in Ruhe. Lange, kryptische Fehlermeldungen sind kein gutes Zeichen für das Debuggen.
Geoff Oxberry
4

Das Problem beginnt bereits mit dem Begriff "Ausdrucksvorlagen (ET)". Ich weiß nicht, ob es eine genaue Definition dafür gibt. Aber in seiner allgemeinen Verwendung verbindet es irgendwie "wie man lineare Algebra-Ausdrücke codiert" und "wie es berechnet wird". Beispielsweise:

Sie codieren die Vektoroperation

v = 2*x + 3*y + 4*z;                    // (1)

Und es wird von einer Schleife berechnet

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

Meiner Meinung nach sind dies zwei verschiedene Dinge, die entkoppelt werden müssen: (1) ist eine Schnittstelle und (2) eine mögliche Implementierung. Ich meine, das ist bei der Programmierung üblich. Sicher (2) mag eine gute Standardimplementierung sein, aber im Allgemeinen möchte ich in der Lage sein, eine spezialisierte, dedizierte Implementierung zu verwenden. Zum Beispiel möchte ich, dass eine Funktion wie

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

werde gerufen wenn ich codiere (1). Vielleicht benutzt (3) nur intern eine Schleife wie in (2). Abhängig von der Vektorgröße können andere Implementierungen jedoch effizienter sein. Auf jeden Fall kann ein Experte für hohe Leistung (3) so viel wie möglich implementieren und optimieren. Wenn also (1) nicht auf einen Aufruf von (3) abgebildet werden kann, dann vermeide ich lieber den syntaktischen Zucker von (1) und rufe sofort (3) direkt auf.

Was ich beschreibe, ist nichts Neues. Im Gegenteil, es ist die Idee hinter BLAS / LPACK:

  • Alle leistungskritischen Vorgänge in LAPACK werden durch Aufrufen von BLAS-Funktionen ausgeführt.
  • BLAS definiert lediglich eine Schnittstelle für die üblicherweise benötigten linearen Algebra-Ausdrücke.
  • Für BLAS existieren verschiedene optimierte Implementierungen.

Wenn der Umfang von BLAS nicht ausreicht (z. B. keine Funktion wie (3) bereitstellt), kann der Umfang von BLAS erweitert werden. So realisiert dieser Dinosaurier aus den 60er und 70er Jahren mit seinem Steinzeitwerkzeug eine saubere und orthogonale Trennung von Schnittstelle und Implementierung. Es ist schon komisch, dass (die meisten) numerischen C ++ - Bibliotheken diese Softwarequalität nicht erreichen. Obwohl die Programmiersprache selbst so viel ausgefeilter ist. Kein Wunder also, dass BLAS / LAPACK noch am Leben und aktiv entwickelt ist.

Meiner Meinung nach sind ETs also nicht böse an sich. Aber wie sie üblicherweise in numerischen C ++ - Bibliotheken verwendet werden, verschaffte ihnen in wissenschaftlichen Rechenkreisen einen schlechten Ruf.

Michael Lehn
quelle
Michael, ich denke, du vermisst eine der Ausdrucksvorlagen. In Ihrem Codebeispiel (1) werden keine optimierten BLAS-Aufrufe zugeordnet. Selbst wenn eine BLAS-Routine vorhanden ist, macht es der Overhead eines BLAS-Funktionsaufrufs für kleine Vektoren und Matrizen ziemlich schrecklich. Ausgefeilte Ausdrucksvorlagenbibliotheken wie Blaze und Eigen können verzögerte Ausdrucksauswertung verwenden, um die Verwendung von Temporären zu vermeiden, aber ich bin überzeugt, dass fast nichts weniger als eine domänenspezifische Sprache in der Lage sein wird, handgerollte lineare Algebra zu schlagen.
Aron Ahmadia
Nein, ich denke, Sie verpassen den Punkt. Sie müssen unterscheiden zwischen (a) BLAS als Spezifikation einer häufig benötigten linearen Algebraoperation (b) einer Implementierung von BLAS wie ATLAS, GotoBLAS usw. Übrigens, wie es in FLENS funktioniert: Standardmäßig würde ein Ausdruck wie (1) durch dreimaligen Aufruf von axpy aus BLAS ausgewertet werden. Aber ohne Änderung von (1) könnte ich es auch wie in (2) bewerten. Logischerweise geschieht Folgendes: Wenn eine Operation wie in (1) wichtig ist, kann die Menge der angegebenen BLAS-Operationen (a) erweitert werden.
Michael Lehn
Der entscheidende Punkt ist also: Notation wie 'v = x + y + z' und wie es schließlich berechnet wird, sollten getrennt werden. Eigen, MTL, BLITZ, blaze-lib scheitern in dieser Hinsicht völlig.
Michael Lehn
1
Richtig, aber die Anzahl der häufig benötigten linearen Algebraoperationen ist kombinatorisch. Wenn Sie eine Sprache wie C ++ verwenden, haben Sie die Wahl, nach Bedarf Ausdrucksvorlagen zu implementieren (dies ist der Eigen / Blaze-Ansatz), indem Sie Unterblöcke und Algorithmen intelligent mit verzögerter Auswertung kombinieren oder eine massive implementieren Bibliothek aller möglichen Routinen. Ich befürworte keinen der beiden Ansätze, da die jüngsten Arbeiten in Numba und Cython zeigen, dass wir mit Skriptsprachen auf hoher Ebene wie Python eine ähnliche oder bessere Leistung erzielen können.
Aron Ahmadia
Aber ich beschwere mich erneut über die Tatsache, dass solch hoch entwickelte (im Sinne von komplizierten, aber unflexiblen) Bibliotheken wie der Eigensche Notations- und Bewertungsmechanismus eng miteinander verbunden sind und dies sogar für eine gute Sache halten. Wenn ich ein Tool wie Matlab verwende, möchte ich nur Dinge codieren und darauf vertrauen, dass Matlab das bestmögliche leistet. Wenn ich eine Sprache wie C ++ verwende, möchte ich die Kontrolle haben. Schätzen Sie also ein, ob ein Standard-Bewertungsmechanismus existiert, der jedoch geändert werden kann. Ansonsten gehe ich zurück und rufe Funktionen in C ++ direkt auf.
Michael Lehn