std::thread langsam



  • freakC++ schrieb:

    Ich arbeite in der Konsole. Da wird nichts geöffnet oder geschlossen 🙂

    Und was sagt time zu deinem Programm? (real, user und sys)



  • real 0m2.736s
    user 0m2.556s
    sys 0m0.144s


  • Mod

    freakC++ schrieb:

    real 0m2.736s
    user 0m2.556s
    sys 0m0.144s

    Da ist etwas ganz furchtbar falsch bei dir oder du verschweigst uns den echten Code. Kein Computer auf Transistorbasis auf der Welt, kann für das gezeigte Programm 2.5 Sekunden Userzeit benötigen. Geschweige denn ein halbwegs moderner Prozessor mit Gigaherztakt. Das ist mindestens um einen Faktor 1000 zu langsam. Oder leitest du die Ausgabe durch einen Nadeldrucker? Nein, das würde auch keine Userzeit benötigen, sondern höchstens Echtzeit fressen. 😕

    P.S.: Du compilierst das Programm doch nicht jedes Mal neu, oder? Denn Compilieren, Optimieren und Linken braucht auf meinem Rechner ca. 1 Sekunde, das könnten 2.5 Sekunden auf einem Atom sein.



  • Doch 🙂 Dann liegt hier ein großes Missverständnis vor, das wegen mir entstanden ist. Sorry, hab mich schlecht ausgedrückt! Ich kompiliere jedes Mal neu. Ist std::thread einfach so schwierig in Maschinencode umzusetzen?

    Danke



  • g++ -std=c++11 -E freaks_programm.cpp | wc -l
    

    Gibt bei mir 34357 aus. 35k Zeilen zu parsen braucht halt schon etwas Zeit. Das zu Linken geht dann schnell.



  • Also ging es um die Kompilezeit und nicht um die Laufzeit?



  • Friedrich Dürrenmatt schrieb:

    Eine Geschichte ist dann zu Ende gedacht, wenn sie ihre schlimmstmögliche Wendung genommen hat



  • freakC++ schrieb:

    void saySomething(const int& x, const int& y) {
            cout << x+y << endl;
    }
    
    int main() {
            int a = 3;
            int b = 8;
            thread t(saySomething,a,b);  // <-- hier
            t.join();
    
            return 0;
    }
    

    Das "hier" ist ja kein Funktionsaufruf von saySomething. Du teilst lediglich dem Konstruktor, mit welchen Parametern er welche Funktion aufzurufen hat. Und weil der Konstruktoraufruf nur dafür sorgt, dass der Thread gestartet wird, kommt es zur weiteren Ausführung des Haupt-Threads schon bevor der neue Thread fertig wird. Ich denke, das ist klar. Aber ich betone das extra nochmal, um zu erklären, wie gefährlich es wäre, wenn das Thread-Objekt sich nur Referenzen merken würde. Beispiel:

    void foo(const int& x)
    {
      pause(123);
      cout << x << endl;
    }
    
    int main()
    {
      thread t (foo, 3+4);
      t.join();
    }
    

    Worauf soll sich x beziehen? Aus 3+4 wird ein temporäres Objekt, dessen Referenz an den Thread-Konstruktor geht. Das temporäre Objekt ist beim Semikolon aber wieder verschwunden. x darf sich also nicht auf dieses temporäre Objekt beziehen, weil es ja schon nicht mehr da ist, wenn in foo die Ausgabe stattfindet.

    Der Thread-Konstruktor behandelt seine Argumente tatsächlich ähnlich wie std::make_tuple. Sie werden kopiert, es sei denn, man hat std::ref oder std::cref verwendet.



  • krümelkacker schrieb:

    Der Thread-Konstruktor behandelt seine Argumente tatsächlich ähnlich wie std::make_tuple. Sie werden kopiert, es sei denn, man hat std::ref oder std::cref verwendet.

    Wenn man es ganz genau nimmt, werden auch die std::ref/cref Rückgaben kopiert - die Kopie eines reference_wrapper ist aber wenig spektakulär, nämlich auch nur eine Referenz auf das selbe Objekt 😉



  • pumuckl schrieb:

    Wenn man es ganz genau nimmt, werden auch die std::ref/cref Rückgaben kopiert

    Das kommt echt auf die Implementierung an, ob diese Dinger kopiert werden oder nicht. Für reference_wrapper machen tuple/make_tuple und thread::thread eine Sonderbehandlung. Beispielsweise liefert make_tuple(cref("x"[0])) ein tuple<const char&> zurück. Für die std::thread Implementierung würde ich wahrscheinlich einfach std::make_tuple benutzen:

    template<class Fun, class...Args>
    thread::thread(Fun&& f, Args&&...args)
    {
      typedef decltype(make_tuple(declval<Fun>(),declval<Args>()...)) tuple_type;
      unique_ptr<tuple_type> up (new tuple_type(forward<Fun>(fun),forward<Args>(args)...));
      if (thread anstossen mit up.get() erfolgreich)
        up.release();
    }
    

    und mir den Kram dann auch in so einem Tuple merken, so dass man das dann über

    template<int...IntSequence>
    struct int_sequence{};
    
    template<class TupleType, class IntSequence>
    struct trampoline;
    template<class TupleType, int...Indices>
    struct trampoline<TupleType,int_sequence<Indices...>>
    {
      static void func(void* context)
      {
        unique_ptr<TupleType> up (static_cast<TupleType*>(context));
        std::get<0>(move(*up))(
          std::get<Indices>(move(*up))...
        );
      }
    }:
    

    im eigentlichen Thread ausführen kann.

    oder so ähnlich mit noch ein bisschen try/catch und Fehlerbehandlung drum herum.

    BTW: Wenn ich mich da nicht vertan habe, ist get<Index>(move(tuple)) äquivalent zu forward<tuple_element<tupletype,Index>::type>(get<Index>(tuple)) . Ersteres ist natürlich leichter aufzuschreiben.


Anmelden zum Antworten