(erledigt) [C++] expliziter Template-Parameter an new?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

(erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

Hi,

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
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?

Beitrag von Aramis »

Nein, das ist nach meinem Verstaendnis der Syntax nicht moeglich. Zumindest nicht in New-Ausdruecken der Form:

Code: Alles auswählen

float dumb;
int* pi = new(dumb) int(4);
… der direkte Aufruf

Code: Alles auswählen

::new<4>(16);
sollte legal sein.
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] expliziter Template-Parameter an operator new?

Beitrag von Krishty »

Danke! Du meinst [ ] statt ( ), oder?

Wie auch immer, schade … dann muss ich zu üblen Type-to-Value-Tricks greifen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4861
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?

Beitrag von Schrompf »

... 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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
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?

Beitrag von Aramis »

Danke! Du meinst [ ] statt ( ), oder?
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
Benutzeravatar
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?

Beitrag von kimmi »

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
Benutzeravatar
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?

Beitrag von Aramis »

Was er meint: es ist (mit Einschraenkungen) legal den operator new als Template zu ueberladen.

Code: Alles auswählen

template <typename T>
void* operator new(size_t num, const T& other) {
 ...
}


std::string* p = new("hi!") std::string();
… 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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

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
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.

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]);
}
Der Optimizer tut in beiden Fällen genau das Gegenteil von dem, was er sollte:

• 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 :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
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?

Beitrag von kimmi »

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
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

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 :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
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?

Beitrag von kimmi »

Da könntest du zum Beispiel mit einem Stackallocator oder einem Smallobject-Allocator natürlich ordentlich Zeit sparen und so... *klugscheiß*

Gruß Kimmi
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
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?

Beitrag von kimmi »

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
Benutzeravatar
Krishty
Establishment
Beiträge: 8251
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: (erledigt) [C++] expliziter Template-Parameter an new?

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten