Globale Variablen in statischen Bibliotheken



  • @ drakon: Die Null-Initialisierung ist für den Fall hier nicht interessant, weil eben die Nebeneffekte (aufgerufene Funktion mit Registrierung) dadurch nicht zum Tragen kommen. Im Prinzip ist der Wert der initialisierten Variable egal, nur der Prozess der Initialisierung an sich ist relevant.

    @ hustbaer: Danke für die Erläuterungen. Den Punkt habe ich sogar auch noch gesehen, aber mir nichts weiter dabei gedacht. Aber eigentlich ist es logisch... Normalerweise versuche ich auch, globale Variablen und frühzeitige Initialisierung genau wegen solcher Dinge zu vermeiden. Aber ab und zu ist das Ganze einfach zu praktisch 🙂

    Allerdings könnte man manche Fälle auch lösen, indem man bei der ersten Verwendung einer Funktion die Initialisierung vornimmt (über eine funktionslokale static -Variable). Vielleicht sollte ich mir das überlegen... Ein static bool in einer Funktion hat wahrscheinlich nicht einmal ein verstecktes if (__initialized) , oder? (Bin mir gerade nicht sicher, ob das auch unter die normale static storage duration geht oder wieder zu irgendeinem Spezialfall gehört)



  • @Nexus:
    Ein "static bool" in einer Funktion wird nur dann kein "if (!initialized)" haben, wenn es konstant initialisiert wird.
    Allerdings wüsste ich nicht wozu man so ein static bool jetzt bräuchte. Ausser um seinen Code thread-unsafe zu machen.

    Verwende gleich das "Meyers Singleton", das ist wenigstens mit GCC thread-safe.
    Bzw. nimm folgenden Code (der vom Prinzip her das selbe macht wie das "Meyers Singleton", nur ohne Singleton *g*)

    int MyInitFunction()
    {
       // ...
       return 42;
    }
    
    void MyPublicFunction()
    {
        static int const s_myInitDummy = MyInitFunction();
        // ...
    }
    

    s_myInitDummy muss hier ein "if (!initialized)" vom Compiler verpasst bekommen, sonst würde es ja immer wieder überschrieben, und das erlaubt der Standard nicht. Dadurch sparst du dir selbst das entsprechende "if" zu schreiben, und bekommst von GCC wie schon gesagt auch threadsicheren Code geschenkt.

    Ansonsten kann man boost::call_once verwenden um es unabhängig vom Compiler thread-safe zu machen.

    Aber davon abgesehen

    Allerdings könnte man manche Fälle auch lösen, indem man bei der ersten Verwendung einer Funktion die Initialisierung vornimmt (über eine funktionslokale static-Variable). Vielleicht sollte ich mir das überlegen...

    Wenn das geht, dann ist ja alles wunderbar.
    Der hier diskutierte Workaround ist vor allem für Fälle gedacht, wo das nicht funktioniert, weil man Dinge vor Programmstart erledigen möchte. z.B. das Registrieren von Plugins/Modulen.

    Bzw. allgemein Fälle, wo man die Nebeneffekte braucht, bevor eine Funktion aufgerufen wird deren Code man anfassen will/kann/darf.



  • hustbaer schrieb:

    Allerdings wüsste ich nicht wozu man so ein static bool jetzt bräuchte. Ausser um seinen Code thread-unsafe zu machen.
    [...]
    s_myInitDummy muss hier ein "if (!initialized)" vom Compiler verpasst bekommen, sonst würde es ja immer wieder überschrieben, und das erlaubt der Standard nicht. Dadurch sparst du dir selbst das entsprechende "if" zu schreiben, und bekommst von GCC wie schon gesagt auch threadsicheren Code geschenkt.

    Ich meinte genau sowas wie s_myIntDummy , aber halt ein bool (spielt ja keine Rolle). Ok, jetzt sehe ich auch, dass bool im dem Zusammenhang verwirrend ist, sorry. Ist so eine static -Konstante auf gcc nun threadsafe oder nicht? Denn irgendwie sehen deine Aussagen für mich wie ein Widerspruch aus 🙂

    Konkret brauch ich das für einen Zufallsgenerator, dessen Seed beim Starten automatisch gesetzt werden soll. Da wäre Threadsicherheit wahrscheinlich nicht schlecht. Allerdings müsste ich dann auch noch einiges mehr threadsicher machen...



  • Ich dachte du willst das "if (!initialized)" selbst schreiben (wegen bool und so, ja, war verwirrend). Das wäre dann nicht threadsafe, es sei denn du kümmerst dich selbst darum (Mutex oder so).

    Wenn du es nicht selbst schreibst, sondern eine function-local static Variable verwendest, dann macht der Compiler es für dich. Und GCC macht es threadsafe. MSVC nicht.

    Was der aktuelle C++0x Draft dazu sagt weiss ich nicht sicher. Ich glaube der sieht das so wie GCC es sieht, nämlich dass es threadsafe sein muss.

    Konkret brauch ich das für einen Zufallsgenerator, dessen Seed beim Starten automatisch gesetzt werden soll. Da wäre Threadsicherheit wahrscheinlich nicht schlecht.

    Zufallsgenerator mach ich immer als Instanzen, keine globalen Funktionen. Daher gibt's das Problem auch nicht.

    Ansonsten ist thread-local Storage angesagt. Sonst kann ein Thread durch das Ziehen von Zahlen aus dem RNG die Sequenz die ein anderer Thread bekommt beeinflussen. -> nicht gut.

    p.S.: und mit thread-local Storage ist thread-safety bei der Initialisierung dann wieder kein Problem, da je eh jeder Thread seine eigenen Variablen hat.



  • hustbaer schrieb:

    Zufallsgenerator mach ich immer als Instanzen, keine globalen Funktionen. Daher gibt's das Problem auch nicht.

    Ja, wäre sicher flexibler, gerade wenn man an verschiedenen Orten voneinander unabhängige Zufallszahlen haben will. Allerdings brauch ich oftmals nur kurz in einem temporären Ausdruck eine Zufallszahl, dafür immer vorher ein Objekt anzulegen ist unpraktisch und liefert auch schlechte Zufallszahlen, wenn er erst gerade mit dem Seed initialisiert wurde. Oder man hätte halt zusätzlich eine globale Instanz, wobei dann wieder das Problem auftritt.

    Wie machst du das, findest du immer einen genügend grossen Kontext, in dem du so ein RNG-Objekt anlegst, oder hast du dann auch häufig lokale "Wegwerf-Objekte"?



  • Nexus schrieb:

    Allerdings brauch ich oftmals nur kurz in einem temporären Ausdruck eine Zufallszahl, dafür immer vorher ein Objekt anzulegen ist unpraktisch und liefert auch schlechte Zufallszahlen, wenn er erst gerade mit dem Seed initialisiert wurde.

    Ja, unpraktisch. Ich habe gerne ein globales Objekt.

    Aber daß der frisch geseedete Zufallszahlengwenerator schlechten Zufall liefert, sehe ich jetzt noch nicht.

    int getSeed(){
       static int seed=time(0);//beim ersten mal uhrzeit lesen
       ++seed;//bei mehreren aufrufen unique machen
    //nicht schlampig verwürfeln, dazu ist der Zufallszahlengenerator besser geeignet
       return seed;
    }
    
    Random::Random(){
       x=getSeed();
       this->rand();//die angekündigte initiale Verwürfelung
    }
    unsigned int Random::rand(){
      x=(x*17+5)%1000;//oder bessere Formel!
      return x;
    }
    


  • Meine Überlegung ist halt, dass Zufallszahlengeneratoren ihre Stärken (z.B. Einhalten einer gegebenen Verteilung, geringe Korrelation) vor allem dann ausspielen, wenn eine grössere Sequenz eingesetzt wird. Einzelne, frisch geseedete Zufallszahlen müssen diese Eigenschaften ja nicht zwingend erfüllen.



  • Nexus schrieb:

    Meine Überlegung ist halt, dass Zufallszahlengeneratoren ihre Stärken (z.B. Einhalten einer gegebenen Verteilung, geringe Korrelation) vor allem dann ausspielen, wenn eine grössere Sequenz eingesetzt wird. Einzelne, frisch geseedete Zufallszahlen müssen diese Eigenschaften ja nicht zwingend erfüllen.

    Ich erwarte, daß die geringe Korrelation zwischen zwei aufeinanderfolgenden Zahlen zu erwarten ist. Deswegen mache ich nur ein rand() im Konstruktor. Ich verlasse mich darauf, daß der Generator recht ordentlich ist, und halte rand(); für nicht schlechter als rand();rand();rand();. Höchstens besser.

    Aber hast recht, die nachgewiesenen Qualitäten des Generators sind bei meiner angedachten Benutzungsart nicht mehr da. Zum Beispiel die unglaubliche Periodenlänge des Mersenne Twisters muß ja weg sein.



  • Nexus schrieb:

    Wie machst du das, findest du immer einen genügend grossen Kontext, in dem du so ein RNG-Objekt anlegst

    Genau das.
    Wobei das in meinen Projekten immer recht einfach war, weil nur an wenigen Stellen wirklich Zufallszahlen gebraucht wurden. Dafür hab' ich schon manchmal verschiedene unabhängige Sequenzen gebraucht, daher würde ich auch nicht (ausschliesslich) mit einem globalen Generator arbeiten wollen.

    oder hast du dann auch häufig lokale "Wegwerf-Objekte"?

    Nö, das wäre mit den Generatoren die wir verwenden (Mersenne Twister) und mit der Art wie wir den Seed holen (CryptGenRandom) auch VIEL zu teuer.



  • Ich frage mich halt einfach, ob man bei

    void Func()
    {
        Randomizer r;
        Use(r.Random());
    }
    

    nicht lieber

    void Func()
    {
        static Randomizer r;
        Use(r.Random());
    }
    

    einsetzen würde (von Threadsicherheit mal abgesehen)? Denn auch wenn man mehrere unabhängige Randomizers jeweils am Anfang verwürfelt, ist die aus verschiedenen Randomizer -Aufrufen resultierende Gesamtsequenz unschön. Es könnte ja sein, dass z.B. immer nur jede Sekunde so ein Aufruf stattfindet, und abhängig von der Qualität des Seeders eine unregelmässige Verteilung oder so auftritt.

    Man könnte zwar argumentieren, dass in so einem Fall ein einzelner Randomizer benutzt werden sollte (wie im Code mit static ), aber dann hat man eben wieder einen Vorteil weniger durch die unabhängigen Objekte.



  • @ hustbaer:
    Okay. Ja, mir scheint sowohl globale als auch unabhängige lokale RNGs haben ihre Vorteile. Vielleicht sollte ich so eine Klasse einrichten und davon eine globale Instanz erzeugen. Diese wird dann benutzt wenn nötig, und in den Fällen mit grösserem Kontext kann ich direkt neue Instanzen einsetzen.



  • Wie wär's mit

    void Foo::Func()
    {
       Use(m_rng.Get());
    }
    
    // oder
    template <class G>
    void Func(G& rng)
    {
       Use(rng.Get());
    }
    
    // oder
    void Func(AbstractRng& rng)
    {
       Use(rng.Get());
    }
    

    ?



  • Gut, vor allem das mit der Memberfunktion könnte ich wohl ab und zu einsetzen. Zum Beispiel könnten Partikel-Emitter, die eine zufällige Abweichung von der Ausstossrichtung haben, nun so einen Generator bekommen. Andererseits ist das eventuell Overkill, da das Resultat wahrscheinlich nicht gross anders aussieht als beim globalen Generator, gerade in Single-Thread-Programmen.



  • Naja, es muss ja nicht gleich ein MT sein (braucht viel Speicher für seinen Zustand) der über teure OS Funktionen (langsam) geseedet wird.

    Ein einfacher LCG wird's da vermutlich tun. Oder der Generator den volkard mal gepostet hat - mir fällt der Name gerade nicht ein (braucht IIRC bloss 2x 32 Bit int's als Zustand und produziert Sequenzen die für viele Anwendungen fast so gut oder besser sind, wie die aus einem MT).

    Und seeden kann man sowas auch trivial, z.B. mit currentFrameNumber ^ reinterpret_cast<size_t>(this) (this = neue Partikel-Emitter-Instanz) oder sowas.



  • Nexus schrieb:

    Gut, vor allem das mit der Memberfunktion könnte ich wohl ab und zu einsetzen. Zum Beispiel könnten Partikel-Emitter, die eine zufällige Abweichung von der Ausstossrichtung haben, nun so einen Generator bekommen. Andererseits ist das eventuell Overkill, da das Resultat wahrscheinlich nicht gross anders aussieht als beim globalen Generator, gerade in Single-Thread-Programmen.

    Leider zerschieße ich mir regelmäßig den globalen Zufallszahlengenerator mit:

    int raki(){
       do
          tuwas();
       while(rand()!=0);
    }
    

    Der Partikel-Emmitter bekommt jetzt nur noch die Zahlen der Zufallsfolge zu sehen, die eine 0 als Vorgänger hatten.
    (Nee, sowas programmiere ich nicht, aber es ist eine süße Vorstellung.)



  • Ich benutze TR1.Random (gefällt mir noch, abgesehen vom verbuggten variate_generator ). Ich habe bisher std::tr1::default_random_engine eingesetzt, was unter MSVC++ dem MT 19937 entspricht. Ist wohl für meine Anwendungen etwas Overkill. Es gibt ja bei Boost eine Tabelle mit Vergleichen, was habt ihr so für Erfahrungen gemacht? Ich denke, der Lineare Kongruenzgenerator hat im Allgemeinen ein akzeptables Verhältnis von Geschwindigkeit und Güte der Zufallszahlen... Oder würdet ihr was Anderes empfehlen?

    Verwendet ihr eigentlich andere Bibliotheken als TR1.Random bzw. Boost.Random?

    Hehe ja, rand() wird ohnehin in allen möglichen Bibliotheken benutzt. Wenn man Wert darauf legt, eine Zufallszahlenfolge für sich allein zu haben, ist das vielleicht nicht ganz das Wahre.



  • Nexus schrieb:

    Ich denke, der Lineare Kongruenzgenerator hat im Allgemeinen ein akzeptables Verhältnis von Geschwindigkeit und Güte der Zufallszahlen... Oder würdet ihr was Anderes empfehlen?

    Außer, es ist so egal, daß man einfach rand() nimmt, würde ich immer was anderes nehmen, denn es gibt viele, die sind auch schnell und haben keinen großen Zustand und sind viel besser.
    Ich mag den MultipyWithCarry am liebsten. Der Xorshift scheint viele Freunde zu haben.



  • Der Multiply With Carry sieht gut aus, er ist nicht zufällig unter einem anderen Namen im TR1? Denn deswegen eine externe Bibliothek benutzen möchte ich eigentlich nicht. Ist er einfach selbst implementiert, oder muss ich lange mit Parametern experimentieren und die Resultate testen? Sonst würde ich vorläufig eher einen fertigen aus TR1 nehmen, bis ich alles in Ruhe ausprobiert habe... 😉



  • Nexus schrieb:

    Der Multiply With Carry sieht gut aus, er ist nicht zufällig unter einem anderen Namen im TR1? Denn deswegen eine externe Bibliothek benutzen möchte ich eigentlich nicht. Ist er einfach selbst implementiert, oder muss ich lange mit Parametern experimentieren und die Resultate testen? Sonst würde ich vorläufig eher einen fertigen aus TR1 nehmen, bis ich alles in Ruhe ausprobiert habe... 😉

    //Multiply-With-Carry Pseudo Random Number Generator
    class Random {
    private:
        static UInt64 const a=4294967118;
        UInt64 x;
    public:
        Random(UInt64 seed=0) {
            x=seed+!seed;
        }
        UInt32 operator()() {
            x=a*(x&0xffffffff)+(x>>32);
            return x;
        }
    };
    
    //Numerical Receipes sagt:
    // Wenn der Seed in 1 <= x <= 2^32-1 liegt
    // und sowohl a*b-1 als auch (a*b-2)/2 Primzahlen sind,
    // dann ist die Periodenlänge gleich (a*b-2)/2.
    //Volkard sagt:
    // Die größte Zahl, die diese Bedingungen erfüllt, ist
    // obiges a=2^32-178 mit beinahe 2^63 als Periodenlänge. 	
    //Mit 1967773755 habe ich den Generator durch die die hard 
    // Tests geschickt und er war großartig! Obiges a habe ich 
    // noch nicht durchgeschickt.
    


  • Cool, vielen Dank 🙂

    Ich wusste nicht, dass das mit so wenig Code geht. Vielleicht schaue ich mir sogar ein DieHard-Testframework an und experimentier ein wenig damit herum. Hast du was dagegen, wenn ich den Code für eine Open-Source-Bibliothek nehme? Wenn du möchtest, kann ich dich auch gerne erwähnen.

    Und dann müsste ich noch schauen, ob ich das mit den 64-Bit einigermassen portabel hinkriege. Ansonsten könnte man das wohl auch mit 32 Bit lösen, evtl. unter In-Kauf-Nahme schlechterer Zufallseigenschaften. Mal schauen... 😉


Anmelden zum Antworten