Gibt es eine Hoffnung, eine gemeinsame Basisklassenmethode für eine std :: -Variante effizient aufzurufen?

8

Die Art, wie std::variant an verschiedene Besuchermethoden versendet, wenn std::visit aufgerufen wird, ist ziemlich vernünftig, wenn die Variantenalternativen völlig unterschiedliche Typen sind. Im Wesentlichen wird eine Besucher-spezifische vtable zur Kompilierungszeit aufgebaut und nach einer Fehlerüberprüfung 1 wird die entsprechende Besucherfunktion durch Indizieren der Tabelle basierend auf der aktuellen index() , die zu etwas wie einem indirekten aufgelöst wird, betrachtet springe auf den meisten Plattformen.

Wenn die Alternativen eine gemeinsame Basisklasse verwenden, ist das Aufrufen einer (nicht virtuellen) Elementfunktion oder des Zugriffs auf den Status der Basisklasse mit einem Besucher konzeptionell viel einfacher: Sie rufen immer das selbe Methode und in der Regel mit dem gleichen Zeiger 2 auf die Basisklasse.

Dennoch endet die Implementierung genauso langsam. Zum Beispiel:

%Vor%

Der generierte Code auf x86 für die neueste Version von gcc und clang ist ähnlich 3 (klingel):

%Vor%

call qword ptr [8*rcx + ... ist der tatsächliche indirekte Aufruf einer Funktion, auf die vtable zeigt (die vtable selbst erscheint am Ende der Liste). Der Code davor prüft zuerst den "ist leer" -Zustand und richtet dann den visit -Aufruf ein (ich bin mir nicht sicher, was die Seltsamkeit mit rdi ist, ich schätze, es richtet einen Zeiger auf den Besucher als den erstes Argument oder etwas).

Die eigentlichen Methoden, auf die der vtable verweist und die von call ausgeführt werden, sind sehr einfach, ein einzelner mov , um das Member zu lesen. Kritisch, beide sind identisch:

%Vor%

Wir haben also ein großes Durcheinander. Um dieses einzelne mov auszuführen, haben wir ein Dutzend Setup-Anweisungen und, was noch wichtiger ist, einen indirekten Zweig: Wenn das Targeting auf eine Reihe von Foobar variant -Objekten mit verschiedenen enthaltenen Alternativen sehr schlecht vorhersagt. Schließlich scheint der indirekte Aufruf eine unüberwindbare Barriere für weitere Optimierungen zu sein: Hier wird ein einfacher Aufruf ohne umgebenden Kontext betrachtet, aber im realen Einsatz könnte dieser zu einer größeren Funktion mit erheblichen Möglichkeiten für weitere Optimierung optimiert werden - aber ich denke indirekt Anruf wird es blockieren.

Du kannst mit dem Code selbst auf godbolt spielen .

Versus einer Union

Die Langsamkeit ist nicht inhärent: Hier ist eine sehr einfache "diskriminierte Union" struct , die die beiden Klassen in union zusammen mit einem isFoo Diskriminator kombiniert, der verfolgt, welche Klasse enthalten ist:

%Vor%

Die entsprechende getBaseMemUnion Funktion kompiliert bis zu einer einzelnen mov Anweisung sowohl für gcc als auch für clang:

%Vor%

Zugegeben, die diskriminierte Gewerkschaft muss die "ist wertlos" -Fehlerbedingung nicht überprüfen, aber das ist nicht der Hauptgrund für variant Langsamkeit, und in jedem Fall ist eine solche Bedingung unmöglich mit Foo und Bar da keiner ihrer Konstruktoren 4 wirft. Selbst wenn Sie einen solchen Zustand unterstützen wollten, ist die resultierende Funktion mit dem union immer noch sehr effizient - nur ein kleiner Haken ist hinzugefügt, aber das Verhalten beim Aufruf der Basisklasse ist das gleiche.

Gibt es etwas, was ich tun kann, um% ce_de% efficient in diesem Fall zu verwenden, wenn man eine allgemeine Basisklassenfunktion aufruft, oder ist das Versprechen einer Null-Kosten-Abstraktion einfach nicht hier herauszuspulen?

Ich bin offen für ein anderes Anrufmuster, Compiler-Optionen, was auch immer.

1 Insbesondere Überprüfung, ob die Variante variant aufgrund einer früheren fehlgeschlagenen Zuweisung ist.

2 Der Zeiger auf die Basisklasse hat nicht immer die gleiche Beziehung zum meist abgeleiteten Zeiger für alle Alternativen, zB wenn Mehrfachvererbung beteiligt ist.

3 Well valueless_by_exception ist ein bisschen schlechter, da es die "is wertlose" Prüfung sowohl vor dem Aufruf von gcc als auch in jeder automatisch erzeugten Methode redundant auszuführen scheint um by visit . Clang macht es nur im Voraus. Denken Sie daran, wenn ich "gcc" sage, ich meine wirklich "gcc mit libstdc ++", während "clang" wirklich "clang mit libc ++" bedeutet. Einige Unterschiede, wie die redundante Überprüfung von vtable in den generierten Besucherfunktionen, sind wahrscheinlich auf Unterschiede in der Bibliothek zurückzuführen, nicht auf Unterschiede bei der Compileroptimierung.

4 Wenn der index() -Zustand problematisch ist, könnte man auch etwas wie valueless die niemals einen leeren Zustand hat, aber trotzdem lokalen Speicher verwendet, wenn der Move-Konstruktor nicht werfen kann.

    
BeeOnRope 20.11.2017, 00:29
quelle

1 Antwort

8

Für was es wert ist, macht ein total handrolled Besuch mit einem switch ziemlich gut:

%Vor%

Was würden Sie gerne verwenden:

%Vor%

Mit VALUELESS wird Folgendes ausgegeben:

%Vor%

Was ziemlich gut ist. Ohne VALUELESS wird Folgendes ausgegeben:

%Vor%

wie gewünscht.

Ich weiß nicht wirklich, welche Schlussfolgerung, wenn überhaupt, daraus zu ziehen ist. Klar, gibt es Hoffnung?

    
Barry 20.11.2017 17:42
quelle

Tags und Links