(erledigt) [C++] expliziter Template-Parameter an new?
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
(erledigt) [C++] expliziter Template-Parameter an new?
Hi,
Titel ist Programm. Geht das? Wenn ja, wie?
Gruß, Ky
Titel ist Programm. Geht das? Wenn ja, wie?
Gruß, Ky
Zuletzt geändert von Krishty am 15.07.2010, 16:19, insgesamt 1-mal geändert.
- Aramis
- Moderator
- Beiträge: 1458
- Registriert: 25.02.2009, 19:50
- Echter Name: Alexander Gessler
- Wohnort: 2016
- Kontaktdaten:
Re: [C++] expliziter Template-Parameter an operator new?
Nein, das ist nach meinem Verstaendnis der Syntax nicht moeglich. Zumindest nicht in New-Ausdruecken der Form:
… der direkte Aufruf
sollte legal sein.
Code: Alles auswählen
float dumb;
int* pi = new(dumb) int(4);
Code: Alles auswählen
::new<4>(16);
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: [C++] expliziter Template-Parameter an operator new?
Danke! Du meinst [ ] statt ( ), oder?
Wie auch immer, schade … dann muss ich zu üblen Type-to-Value-Tricks greifen.
Wie auch immer, schade … dann muss ich zu üblen Type-to-Value-Tricks greifen.
- Schrompf
- Moderator
- Beiträge: 4884
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas Ziegenhagen
- Wohnort: Dresden
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
... ich versteh nichtmal die Frage. Was soll damit denn erreicht werden?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Einen Alignment-Wert übergeben.
Die sind Compile-Time-constant – nur, indem sie schon während des Kompilierens ausgewertet werden, werden sie auch mit Sicherheit ordentlich optimiert.
Die sind Compile-Time-constant – nur, indem sie schon während des Kompilierens ausgewertet werden, werden sie auch mit Sicherheit ordentlich optimiert.
- Aramis
- Moderator
- Beiträge: 1458
- Registriert: 25.02.2009, 19:50
- Echter Name: Alexander Gessler
- Wohnort: 2016
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Wieso sollte ich? Darf ich neuerdings einen einzelnen int nicht mehr auf dem Heap anlegen und auf 4 initialisieren wenn mir das Spaß macht? :DDanke! Du meinst [ ] statt ( ), oder?
- kimmi
- Moderator
- Beiträge: 1405
- Registriert: 26.02.2009, 09:42
- Echter Name: Kim Kulling
- Wohnort: Luebeck
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Ich steh auch auf dem Schlauch. Kannst du mal den Anwendungsfall deinerseits kurz umreißen? ich lese nachts nur selten den C++-Standard ;).
Gruß Kimmi
Gruß Kimmi
- Aramis
- Moderator
- Beiträge: 1458
- Registriert: 25.02.2009, 19:50
- Echter Name: Alexander Gessler
- Wohnort: 2016
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Was er meint: es ist (mit Einschraenkungen) legal den operator new als Template zu ueberladen.
… hier wuerde der Compiler new<T> implizit mit T = const char* instanzieren. Krishty moechte den Templateparameter aber explizit angeben, was syntaktisch nicht klappt. Außer direkt das globale ::new<T>-Aufrufen und mit placement-new den c'tor aufrufen, was auch legal waere.
Code: Alles auswählen
template <typename T>
void* operator new(size_t num, const T& other) {
...
}
std::string* p = new("hi!") std::string();
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Ups. Ich habe so lange keine einzelnen Elemente mehr allokiert, dass ich scheinbar nicht mehr wusste, wie das aussieht … vielleicht habe ich auch gesehen, dass es Post #666 war und angenommen, dass der initialböse sei :D ’tschuldigung.Aramis hat geschrieben:Wieso sollte ich? Darf ich neuerdings einen einzelnen int nicht mehr auf dem Heap anlegen und auf 4 initialisieren wenn mir das Spaß macht? :D
Auch das, was ich machen will, hast du korrekt wiedergegeben.
Nochmal was Genaueres zu meinen Beweggründen, damit ihr mich nicht wieder für verkalkt (sondern stattdessen für exzentrisch-perfektionsorientiert) haltet:
Der Compiler (VC 2010) prüft, ob sich das Inlining einer Funktion lohnen würde, bevor er den Ausführungspfad einer Funktion auf den Aufruf hin optimiert. Das bedeutet: Kommt in der Funktion zur ausgerichteten Allokierung viel Integer-Arithmetik vor, kann es sein, dass der Compiler meint, sie würde zu schwer für Inlining obwohl sich all diese Arithmetik zu nur ein oder zwei Compile-Time-constant-Expressions auflösen würden, wenn der Compiler sie inlinen würde.
Vereinfachtes Real-Life-Beispiel von gestern:
Code: Alles auswählen
class CIntArray {
int * MyBegin;
size_t MyLength;
size_t MyCapacity;
void ReallocateTo(size_t NewCapacity) {
// Diese Funktion wird MÄCHTIG. ZU mächtig für Inlining. Sie muss alle Fälle behandeln; Allokation, Reallokation und Freigabe.
if(nullptr != MyBegin)
if(0 < NewCapacity)
// Reallokation
else
// Freigabe
else
if(0 < NewCapacity)
// Erst-Allokation
else
// Garnichts tun
MyCapacity = NewCapacity;
}
public:
// Leer erzeugen
CIntArray()
: MyBegin(nullptr)
, MyLength(0)
, MyCapacity(0)
{ }
// Kapazität reservieren um später Allokationen zu sparen
CIntArray(size_t ItsCapacity)
: MyBegin(nullptr)
, MyLength(0)
, MyCapacity(ItsCapacity)
{
ReallocateTo(ItsCapacity);
}
void push_back(int NewInt) {
if(MyCapacity < MySize + 1)
ReallocateTo(MyLength + 1);
MyBegin[MyLength++] = NewInt;
}
~CIntArray() {
ReallocateTo(0);
MyLength = 0;
}
};
…
// Erste Anwendung: Einmalig befüllen
CIntArray Array;
Array.push_back(1);
// Zweite Anwendung: In Schleife befüllen
CIntArray Indices(3 * NumberOfTriangles);
for(…) {
Indices.push_back(Triangle.Indices[0]);
Indices.push_back(Triangle.Indices[1]);
Indices.push_back(Triangle.Indices[2]);
}
• Im ersten Fall wird der Optimizer push_back(1) inlinen. Klappt immer, denn ist sehr kurz. Dann wird er nachsehen, ob sich ReallocateTo() inlinen ließe – und wird allein aufgrund der Größe der Funktion entscheiden, dass das ineffizient wäre. Würde er es aber tun, dann würde er – da er an dieser Stelle weiß, dass MyCapacity und MyBegin 0 sind – drei der vier Ausführungspfade wegoptimieren können und nur den mit der Erst-Allokation behalten, womit die Funktion wieder perfekt für Inlining wäre.
• Im zweiten Fall wird der Optimizer die drei push_back()s inlinen. Jetzt wird es interessant: Innerhalb von Schleifen hat der Compiler eine viel niedrigere Hemmschwelle zum Inlining, darum wird er auch ReallocateTo() inlinen. Allerdings kann er dafür die Werte von Variablen weniger präzise vorhersagen und wird keinen der Ausführungspfade wegoptimieren. Das bedeutet: Obwohl das Array vor der Schleife absichtlich so präperiert wurde, dass keine Allokation, Reallokation oder Freigabe in der Schleife nötig ist, wird der Compiler Code für genau diese drei Fälle in der Schleife inlinen, und zwar für alle drei Aufrufe. (Unwichtiges Detail: Den Erst-Allokationsaufruf hat er beim ersten push_back() weggelassen, ich habe aber keine Ahnung, warum.) Obwohl die Sprungvorhersage der CPU zur Laufzeit wahrscheinlich kein Byte dieses Codes jemals ansteuern wird, dürften die zehnfach überhöhte Code-Größe und der höhere Registerdruck der Performance nicht gerade zuträglich sein. Und ironischerweise werden die einzigen beiden Aufrufe an ReallocateTo(), die jemals ausgeführt würden (nämlich vor der Schleife beim Reservieren und hinter der Schleife beim Zerstören des Arrays) nicht geinlined (denn außerhalb von Schleifen ist die Funktion zu groß zum Inlining). Indem ich das erkannt habe, konnte ich gestern die Größe einer Funktion auf ein Fünftel reduzieren.
Und darum möchte ich, dass mein operator new, der momentan an 200 Stellen geinlined wird, trotz Alignment-Berechnungen weiterhin für Inlining in Betracht gezogen wird indem all die Integer-Operationen, die bei einem gewöhnlichen Parameter nötig wären, durch Template-Auflösung ersetzt werden :)
- kimmi
- Moderator
- Beiträge: 1405
- Registriert: 26.02.2009, 09:42
- Echter Name: Kim Kulling
- Wohnort: Luebeck
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Ok, nun habe ich das verstanden. Danke für die Erklärung. Erhöht dein relocateTo die Buffergröße eigentlich immer um 1 je push-back? Im Code scheint das auf den ersten Blick so zu sein. Wenn ja: warum :). Wenn nein: vergiss die Frage...
Gruß Kimmi
Gruß Kimmi
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Ja, tut es; aber nur der Einfachheit des Beispiels halber – die reale Implementierung ist natürlich ein schicker Template-Container, in dem noch mehr Verwaltungslogik steckt, u.A. auch eine ordentliche Reservierung und Wachstumsrate :)
- kimmi
- Moderator
- Beiträge: 1405
- Registriert: 26.02.2009, 09:42
- Echter Name: Kim Kulling
- Wohnort: Luebeck
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Da könntest du zum Beispiel mit einem Stackallocator oder einem Smallobject-Allocator natürlich ordentlich Zeit sparen und so... *klugscheiß*
Gruß Kimmi
Gruß Kimmi
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Glaub mir – wenn ich soweit gehe und den Compiler darüber belehre, wie er richtig zu inlinen hat, dann ist zwischen den unnötigen Funktionsaufrufen schon nicht mehr vorhanden als ein paar movs und incs.
- kimmi
- Moderator
- Beiträge: 1405
- Registriert: 26.02.2009, 09:42
- Echter Name: Kim Kulling
- Wohnort: Luebeck
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Das denke ich mir :). Schaust du in dem Kontext auch gleich nach der Performance der Algorithmen oder machst du das dann iterativ mit einem Profiler?
Gruß Kimmi
Gruß Kimmi
- Krishty
- Establishment
- Beiträge: 8268
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: (erledigt) [C++] expliziter Template-Parameter an new?
Naja, das ist so eine Sache … momentan ist da einfach zu wenig zu benchen oder zu profilen – gemäß meinem letzten Profiling im April verbrachte die Sternen-Rendering-Demo so viel Zeit in ihren zehn Matrixmultiplikationen wie in der kompletten restlichen Hauptschleife zusammen -.- Das Beispiel mit den Indizes von oben operierte auch nicht auf mehr als 200 Dreiecken. Auf so winzigen Zeitintervallen und Datenmengen kann und will ich nicht profilen.
Benchmarks sind immer realitätsfern … das Beispiel mit der falsch geinlineten Reallokation hätte im Benchmark ganz anders ausgesehen – dort hätte ich mehrfaches Befüllen in der Schleife wahrscheinlich garnicht getestet und die Allokation im Konstruktor einfach durch __forceinline erzwungen. Ich glaube, dass solche Sachen dann ziemlich in die Hose gehen können und man den Fehler lange Zeit nicht merkt denn, „das ist optimiert, der Bottleneck liegt sicher woanders“.
Im Moment steppe ich einfach den Assemblercode der voll optimierten Exe durch und schaue grob, wo ich welche Funktion wiedererkenne und ob irgendein Zweig verdächtig kompliziert aussieht (dabei sieht man schnell, wo man Move-Konstruktoren gebrauchen kann). Wenn ich dann sehe, dass der Compiler das Programm etwa so umgesetzt hat wie ich mir das vorgestellt habe (was meist der Fall ist – es gab aber auch Fälle, als der Compiler komplett andere Überladungen von Funktionen aufgerufen hat als ich vorsah), vertraue ich ihm vollends, dass es nicht allzu langsam laufen wird. Um aber auf Registerebene einzelne Funktionen auf Effizienz zu prüfen fehlen mir Lust und, vor allem, Wissen und Erfahrung.
Was ich gern als Maßstab nehme, ist die Größe der kompilierten Exe. Es ist natürlich völlig illusorisch, daraus irgendwie auf die Ausführungsgeschwindigkeit zu schließen, aber wenn ich unterschiedliche Versionen meines Programms mit identischen, auf Geschwindigkeit optimierten Compiler-Einstellungen und gleicher Ausführungsstruktur nebeneinander stelle und sehe, dass das Kompilat nach dem Ersetzen der Standard-Exception-Klassen (die bei VC eh miserabel umgesetzt sind) durch eine eigene Klasse um 25 % kleiner geworden ist, oder nach dem Rausschmeißen unnützen Paddings aus Datenstrukturen 5 %, dann kann ich mir ziemlich sicher sein, dass es außerdem zumindest nicht langsamer geworden ist.
Wenn alles ausreichend umfangreich ist, steige ich sicher auf Profiling um. Ich hatte letztens woanders den Fall, dass die CPU 98,5 % der Ausführungszeit des Algorithmus in einer einzigen Zeile einer äußeren Schleife verbracht hat – sowas erkennt man nicht beim Planen, nicht beim Überfliegen des Assembler-Codes und nicht durch Rumprobieren (jedenfalls nicht schnell und bequem), sondern nur durch Profiling.
Benchmarks sind immer realitätsfern … das Beispiel mit der falsch geinlineten Reallokation hätte im Benchmark ganz anders ausgesehen – dort hätte ich mehrfaches Befüllen in der Schleife wahrscheinlich garnicht getestet und die Allokation im Konstruktor einfach durch __forceinline erzwungen. Ich glaube, dass solche Sachen dann ziemlich in die Hose gehen können und man den Fehler lange Zeit nicht merkt denn, „das ist optimiert, der Bottleneck liegt sicher woanders“.
Im Moment steppe ich einfach den Assemblercode der voll optimierten Exe durch und schaue grob, wo ich welche Funktion wiedererkenne und ob irgendein Zweig verdächtig kompliziert aussieht (dabei sieht man schnell, wo man Move-Konstruktoren gebrauchen kann). Wenn ich dann sehe, dass der Compiler das Programm etwa so umgesetzt hat wie ich mir das vorgestellt habe (was meist der Fall ist – es gab aber auch Fälle, als der Compiler komplett andere Überladungen von Funktionen aufgerufen hat als ich vorsah), vertraue ich ihm vollends, dass es nicht allzu langsam laufen wird. Um aber auf Registerebene einzelne Funktionen auf Effizienz zu prüfen fehlen mir Lust und, vor allem, Wissen und Erfahrung.
Was ich gern als Maßstab nehme, ist die Größe der kompilierten Exe. Es ist natürlich völlig illusorisch, daraus irgendwie auf die Ausführungsgeschwindigkeit zu schließen, aber wenn ich unterschiedliche Versionen meines Programms mit identischen, auf Geschwindigkeit optimierten Compiler-Einstellungen und gleicher Ausführungsstruktur nebeneinander stelle und sehe, dass das Kompilat nach dem Ersetzen der Standard-Exception-Klassen (die bei VC eh miserabel umgesetzt sind) durch eine eigene Klasse um 25 % kleiner geworden ist, oder nach dem Rausschmeißen unnützen Paddings aus Datenstrukturen 5 %, dann kann ich mir ziemlich sicher sein, dass es außerdem zumindest nicht langsamer geworden ist.
Wenn alles ausreichend umfangreich ist, steige ich sicher auf Profiling um. Ich hatte letztens woanders den Fall, dass die CPU 98,5 % der Ausführungszeit des Algorithmus in einer einzigen Zeile einer äußeren Schleife verbracht hat – sowas erkennt man nicht beim Planen, nicht beim Überfliegen des Assembler-Codes und nicht durch Rumprobieren (jedenfalls nicht schnell und bequem), sondern nur durch Profiling.