rand&co. (geteilt aus: Frage zu lvalue / rvalue Referenzen)



  • Verstehe, was du meinst, muss man halt abwägen.
    Das ist Stroustrups Beispielcode, von dem sich meiner ableitet:

    class Rand_int {
    public:
        Rand_int(int low, int high) :dist{low,high} { }
        int operator()() { return dist(re); } // einen int ziehen
    private:
        default_random_engine re;
        uniform_int_distribution<> dist;
    };
    

    Deshalb auch die Wahl der Engine.

    Edit: Was Seeding oder Serialisierung ist, wurde im Buch noch nicht erklärt, deshalb ist es auch nicht eingebaut. Da kommen aber trotzdem Zufallszahlen raus, auch wenn ich nichts von Beidem tue.



  • Und warum ist <random> ungeschickt?

    Ich bin auch nicht der Meinung, dass dein Code Schnipsel Anfängerfreundlich ist. Es fehlen Header und der ist Compiler spezifisch. Vorkompilierte Header würde ich Anfängern nicht empfehlen, denn um das zu verstehen muss man den Buildprozess schon halbwegs verstanden haben.

    Außerdem gefällt mir der Mischmasch aus deutschen und englischen Bezeichnern nicht.

    Es ist insgesamt ein einaches Beispiel von Stroustrup wie man einen gleichverteilten ganzzahlen Zufallszahlen generator erstellen kann. Zur Übung und zum Lernen ist das alles sicher in Ordnung, aber ich würde Anfängern raten, es nicht einfach zu kopieren und zu benutzen, sondern sich mit dem Thema Zufallszahlen selbst zu beschäftigen.



  • Ich seh's ein, der Code ist wohl eher speziell für mich eine gute Übergangslösung (bis ich zum entsprechenden Kapitel komme).
    Trotzdem glaube ich nicht, dass es einen Anfänger juckt, ob die Details plattformabhängig sind. Eher Fortgeschrittene. Mir z.B. ist es ehrlich gesagt völlig schnuppe.



  • Naja, ein Anfänger möchte Code haben, der aus jeden Fall funktioniert. Nicht Code, der nur unter bestimmten Bedingungen funktioniert. Du bindest ja zum Beispiel nicht <random> ein, daher finde ich es eh erstaunlich, dass dein Code überhaupt kompiliert.

    Wird dann vermutlich mit Visual Studio zusammenhängen?

    Nächste Sache: warum werden bei dir die int-Variablen als const int& übergeben? In Stroustrups Beispiel ist das nicht so.

    Was man für Anfänger überlegen könnte, wäre sowas:

    class EasyRandom {
    public:
      EasyRandom();
      int uniform_int(int min, int max);
      double uniform_double(double min, double max);
    private:
      std::mt19937 mt;
    };
    

    Das spiegelt auch wieder, dass die uniform_xxx_distribution schnell erstellt werden kann, während der Generator-Constuctor selbst teuer ist.

    Andererseits: wenn man sich den Random-Header einmal etwas näher anschaut, sind Generatoren/Verteilungen einfach zu benutzen. Das einzig komplizierte ist ordentliches Seeden. Ich habe noch nicht rausgefunden, wie viel/was ich mindestens in eine seed_seq tun muss, um z.B. den mt19937-Initialstate vollständig zufällig hinzubekommen.



  • Ja, hatte vergessen, dass <random> im vorkompilierten Header ist.
    Die Argumente werden nicht verändert und müssen nicht kopiert werden. Deshalb const int&.
    Deinen Beispielcode versteh ich nicht, wohl eher nicht so geeignet 😞



  • Timon Paßlick schrieb:

    Die Argumente werden nicht verändert und müssen nicht kopiert werden. Deshalb const int&.

    Kopieren ist hier aber mindestens so schnell wie die zusätzliche Indirektion. Daher nehme ich fundamentale Typen prinzipiell immer by Value, nicht by Const-Reference.

    Timon Paßlick schrieb:

    Deinen Beispielcode versteh ich nicht, wohl eher nicht so geeignet 😞

    Du hättest stattdessen sagen können, was du daran nicht verstehst, damit dir geholfen werden kann. Ich sehe nicht ganz, wie du deinen Code aber nicht wob's Code verstehen kannst.

    LG



  • Was machen die 2 Funktionen?



  • Timon Paßlick schrieb:

    Was machen die 2 Funktionen?

    Die erste soll einen gleichverteilten int im angegebenen Bereich liefern, die zweite einen gleichverteilten double.

    Vielleicht hätte ich die besser generate_uniform_int bzw. generate_uniform_double genannt.

    Man könnte dann z.B. double generate_gaus(double mean, double sigma) hinzufügen usw. Hebt man halt die Trennung von Generator und Distributionen ein bisschen auf, dafür bekommt man Syntax-Vervollständigung von der IDE, wenn man den "my_rand_object." tippt.

    Dann braucht man nämlich nur ein einfaches "Zufallsobjekt" und kann mit diesem beliebige Ranges an Zufallszahlen berechnen. Es erschien mit wenig sinnvoll, die distribution mit in das Objekt aufzunehmen*. Aber wie gesagt, wir sind dann dicht am Original dran. Lediglich das Initialisieren hätte man dann schön versteckt.

    * ich habe sogar mal einen Benchmark gesehen, wo es besser war, die distibution innerhalb einer Schleife zu erzeugen als außerhalb - offenbar hat der Optimizer das dann besser optimieren können. Daher keine Angst vorm Erzeugen der distribution-Objekte.



  • Ich finde das immer interessant, wie komisch das ist mit den Compilern. Die können immer andere Dinge gut optimieren, als man denkt. Mir wurde das z.B. so erklärt:
    Muss nicht kopiert werden -> Referenz setzen.
    Konstruktoren selten aufrufen.
    Ich bin richtig verwirrt. Muss man Assembler können und die Compiler haargenau kennen, um schnellen Code zu schreiben?
    Apropos: Warum überlegt der Compiler eigentlich nur, ob er Funktionen in einfachen Code umschreiben kann, wenn inline davorsteht? Und sollte ich vor jede Funktion inline schreiben?
    Edit: Ich weiß, das ist wieder ein anderes Thema, aber der Thread ist sowieso schon abgeschwiffen. 🙂



  • Timon Paßlick schrieb:

    Warum überlegt der Compiler eigentlich nur, ob er Funktionen in einfachen Code umschreiben kann, wenn inline davorsteht?

    Deine Annahme stimmt nicht.

    Siehe z.B. http://en.cppreference.com/w/cpp/language/inline
    Lies insbesondere den Text ab "The original intent"...

    Ähnlich wie das "register"-Keyword. Das nun in C++17 erstmal ganz ungenutzt ist.



  • Kann ich aus deiner Antwort schließen, dass inline irrelevant ist und ich es schreiben und lassen kann, wie ich möchte?



  • Nein. inline ist für Inlining irrelevant. Wenn du eine Funktion im Header definierst, so musst du nach wie vor inline schreiben, um die ODR nicht zu verletzen.
    Des weiteren gibts seit C++17 gibt auch noch inline Variablen.



  • Timon Paßlick schrieb:

    Ich finde das immer interessant, wie komisch das ist mit den Compilern. Die können immer andere Dinge gut optimieren, als man denkt. Mir wurde das z.B. so erklärt:
    Muss nicht kopiert werden -> Referenz setzen.
    Konstruktoren selten aufrufen.
    Ich bin richtig verwirrt. Muss man Assembler können und die Compiler haargenau kennen, um schnellen Code zu schreiben?

    Muss nicht, aber es hilft natürlich.
    Was wichtig ist, ist zu verstehen wie Computer "intern" arbeiten, wie lange bestimmte Dinge so ca. dauern, und dass man halbwegs einschätzen kann was ein C++ Compiler für Code generieren wird. Dabei hilft es natürlich ungemein wenn man Assembler kann - zumindest lesen.
    Das heisst jetzt nicht unbedingt dass man jede Instruktion kennen muss die eine bestimmte CPU so kann. Aber das Grobe, also wie Maschinencode so grundsätzlich funktioniert, das wäre schon gut.
    Und das ist auch nicht SO schwer zu lernen 😉

    Timon Paßlick schrieb:

    Apropos: Warum überlegt der Compiler eigentlich nur, ob er Funktionen in einfachen Code umschreiben kann, wenn inline davorsteht?

    Ohne spezielle, non-standard Settings, können Compiler nur Funktionen inlinen, deren Definition sie auch in der selben Translation-Unit "gesehen" haben.
    Wenn du also eine Funktion Foo in Foo.cpp definierst (und in Foo.h nur deklarierst), und Foo() dann in Bar.cpp aufrufst, dann kann der Compiler kein Inlining machen. Weil er die Definition von Foo() beim Übersetzen von Bar.cpp ja nicht sehen kann.

    Wenn du die entsprechenden Compiler-Optionen mitgibst, dann versucht der Compiler gleich gar keinen Code mehr zu erzeugen, sondern überlässt diese Aufageb dem Linker. Nennt sich "Whole Program Optimization" bzw. "Link Time Code Generation". Dabei kann dann auch der oben beschriebene Aufruf von Foo() in Bar.cpp geinlined werden.

    Und natürlich gibt es Compiler-Settings mit denen man dem Compiler explizit verbieten kann Funktionen zu inlinen die nicht mit "inline" definiert wurden. (MSVC zumindest hat so eine Option.)

    Timon Paßlick schrieb:

    Und sollte ich vor jede Funktion inline schreiben?

    Ich würde inline eher nur für Funktionen verwenden wo du es wegen der ODR verwenden musst.
    Wenn es super-wichtig ist dass die Funktion ganz sicher garantiert immer inline erweitert wird, dann kannst du BOOST_FORCEINLINE verwenden. Da bekommst du dann normalerweise ne Warning wenn die Funktion nicht inline erweitert wird (weil der Compiler es nicht kann).
    Damit kann man sich aber auch selbst in den Fuss schiessen, weil Inlining nicht immer gut ist. Kommt halt drauf an. Und wenn man nicht vor hat sich lange laaaaaaange damit zu beschäftigen, dann sollte man die Wahl besser dem Compiler überlassen.

    (Was dagegen noch öfter Sinn machen kann ist BOOST_NOINLINE - womit man dem Compiler verbietet eine bestimmte Funktion inline zu erweitern.)



  • Timon Paßlick schrieb:

    Ich bin richtig verwirrt. Muss man Assembler können und die Compiler haargenau kennen, um schnellen Code zu schreiben?

    Kommt darauf an, was du mit "schnell" meinst. Wenn du gut skalierende und solche Algorithmen und Datenstrukturen wählst, die zu deinem Problem passen, dann wirst du aller Wahrscheinlichkeit schon schneller sein als die meisten Konkurrenzprodukte (sofern es denn welche gibt). Und damit für die meisten Ansprüche "schnell genug".

    Darüber hinaus: ja, du musst nicht nur deine Compiler sehr gut kennen, sondern auch deine Zielarchitekturen. Dass du Assembler lesen können musst, ist klar, aber auch keine große Hürde.
    Wenn man die Hardware optimal ausnutzt, lassen sich zwar meist noch massive Performancesteigerungen erzielen. Selbst erfahrene Entwickler unterschätzen reihenweise das schiere Potential moderner Architekturen wie Haswell, Skylake oder auch Zen. Die Frage ist, ob sich das für dich lohnt - denn um die Hardware im Zusammenspiel mit dem Compiler optimal auszureizen, brauchst du Jahre an Erfahrung und Fachwissen. Zudem kommst du auch schon sehr weit, indem du Code von fremden Leuten mit Ahnung verwendest, beispielsweise Intels IPP.

    Aber: zumindest die Grundlagen sollte jeder Entwickler kennen, der kein Anfänger mehr ist. Alles andere halte ich fast schon für unverantwortlich.
    Du solltest wissen, welche grundlegende Arten von Optimierungen man von Compilern erwarten kann und du solltest grundlegendes Verständnis von CPU-Caches und der Pipeline haben.
    Mit diesem Wissen kannst du schon die gröbsten Fehler vermeiden.



  • Ich will mich ja auf x86(_64) spezialisieren. Dann hab ichs mit unterschiedlichen Prozessoren zu tun. Die muss ich aber nicht alle kennen, oder?



  • Timon Paßlick schrieb:

    Ich will mich ja auf x86(_64) spezialisieren. Dann hab ichs mit unterschiedlichen Prozessoren zu tun. Die muss ich aber nicht alle kennen, oder?

    Nein, nur die Architektur an sich ist wichtig (also z.B. Skylake) und nicht die konkreten Modelle. Unterschiede zwischen etwa Haswell und Skylake gibt es durchaus, aber im Schnitt sind die nicht dramatisch. Und erfreulicherweise soll sich die AMD Zen-Architektur an Haswell orientieren, sodass für Haswell kompilierte Programme gut auf Zen laufen sollen. Konnte ich mangels Zen-CPU noch nicht überprüfen, aber im Idealfall bedeutet das, dass man in Zukunft nicht mal mehr zwischen AMD und Intel unterscheiden muss.

    Also orientierst du dich immer an der neuesten Architektur, die du auch selbst testen kann, also meist der CPU deines Entwicklungsrechners.



  • hustbaer schrieb:

    Was wichtig ist, ist zu verstehen wie Computer "intern" arbeiten, wie lange bestimmte Dinge so ca. dauern, ...

    Da es scheinbar noch keiner gepostet hat:

    http://norvig.com/21-days.html#answers

    Neben der Verwendung effizienter Algorithmen ist diese Tabelle meiner Meinung nach eines der ersten Dinge,
    die man berücksichtigen sollte, bevor man allzu tief in die Details spezifischer Architekturen einsteigt.

    Auch cool: Visualisierung der Latenzen mit Slider, wo man schön sehen kann, wie sich die Werte über die Jahre in etwa verändert haben:

    https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html



  • Ist x86 eine Architektur oder bring ich was durcheinander?
    Und wenn ja, ist x86_64 eine andere Architektur?
    Und wenn nochmal ja, wie viel ist die anders?

    Die Tabelle ist übrigens cool. Endlich mal Zahlen. Auch wenn ich nicht bei jeder Zeile versteh, was das ist, es hilft mir.


  • Mod

    x86 ist ein gängiger Sammelbegriff für die 386-artigen 32-Bit Architekturen. Davon gibt es sehr viele Varianten, die aber alle halbwegs kompatibel sind, d.h. neuere Versionen davon können auch Code ausführen, der für ältere Varianten gedacht war.

    x86_64 ist die 64-Bit Variante davon. Ein herausragendes Feature dieser Architekturen ist, dass sie auch ganz gut mit Code für x86 zurecht kommen, was einer der Gründe ist, weshalb diese Architektur sich für Desktopcomputer durchgesetzt hat.



  • ...und da wir im C++-Forum sind, schau dir einfach mal die Optionen an, die es hier so gibt:
    https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Submodel-Options.html

    Du siehst, x86 ist hier ein Sammelbegriff für eine recht große Menge an Unteroptionen.


Anmelden zum Antworten