for-Optimierung



  • cooky451 schrieb:

    Wenn er sieht, dass foo() immer das Gleiche zurückgibt, sehr wahrscheinlich, ja. Bitte aber auch bedenken, dass das hier wesentlich schneller ist, falls die Reihenfolge nicht wichtig ist:

    for (auto i = foo(); i-- != 0; )
    

    das ist nice, muss ich mir merken!(sorry für den doppelpost)



  • Namenloser324 schrieb:

    cooky451 schrieb:

    Wenn er sieht, dass foo() immer das Gleiche zurückgibt, sehr wahrscheinlich, ja. Bitte aber auch bedenken, dass das hier wesentlich schneller ist, falls die Reihenfolge nicht wichtig ist:

    for (auto i = foo(); i-- != 0; )
    

    das ist nice, muss ich mir merken!(sorry für den doppelpost)

    Merk dir es, aber verwende es sogut wie nie. Man sollte immer gut lesbaren Code vorziehen. Außerdem ist es ganz sicher nicht wesentlich schneller. Zu 90% ist die Schleife für Performance sowieso unwichtig.
    http://en.wikipedia.org/wiki/Program_optimization#Bottlenecks

    In computer science, the Pareto principle can be applied to resource optimization by observing that 80% of the resources are typically used by 20% of the operations. In software engineering, it is often a better approximation that 90% of the execution time of a computer program is spent executing 10% of the code (known as the 90/10 law in this context).



  • In welcher Hinsicht ist das bitte nicht gut lesbar? Oo



  • Also ich würde mich auch genötigt fühlen, dieses Konstrukt länger zu durchdenken und danach einen Kommentar drüber zu schreiben und _danach_ die Schleife durch die lesbar Variante zu ersetzen.

    //edit es ist nichtmal klar, ob der Compiler checkt was du machen willst. Im worst case ist er verwirrt und schaltet dir erstmal SSE aus.



  • otze schrieb:

    Also ich würde mich auch genötigt fühlen, dieses Konstrukt länger zu durchdenken und danach einen Kommentar drüber zu schreiben und _danach_ die Schleife durch die lesbar Variante zu ersetzen.

    Aua.

    otze schrieb:

    //edit es ist nichtmal klar, ob der Compiler checkt was du machen willst. Im worst case ist er verwirrt und schaltet dir erstmal SSE aus.

    Bitte was? Natürlich rafft der das. 🙄



  • Warum Aua? Dieses Konstrukt ist so unnnatürlich, dass ich erstmal analysiere, ob es überhaupt einen Sinn hat, rückwärts zu iterieren. Das kostet mich im Zweifelsfall, je nach Codekenntnis, 10-30 Minuten, bevor ich daran weiter arbeiten kann. Das ist genauso, wie bei Leuten, die ihre Laufvariablen vor dem Loop deklarieren wo ich den ganzen Folgecode durchsuche, ob der Wert der Variablen noch irgendwo benutzt wird.

    An so ein Konstrukt gehört ein Kommentar. Mit Begründung(also Benchmark).

    Und zu dem Zweiten Teil: das hast du natürlich getestet? Ich habe da nicht mehr so viel Vertrauen in meine Compiler.



  • otze schrieb:

    Warum Aua? Dieses Konstrukt ist so unnnatürlich,

    What? 😮 Ich benutze das in etwa 10 mal öfter als das "normale" Konstrukt, und offen gesagt finde ich es wesentlich leichter zu lesen. Ich bevorzuge es auch, wenn Performance garkeine Rolle spielt.

    otze schrieb:

    dass ich erstmal analysiere, ob es überhaupt einen Sinn hat, rückwärts zu iterieren. Das kostet mich im Zweifelsfall, je nach Codekenntnis, 10-30 Minuten, bevor ich daran weiter arbeiten kann.

    Wie lang sind denn deine Schleifen, dass man da 10 Minuten dran lesen kann?

    otze schrieb:

    Das ist genauso, wie bei Leuten, die ihre Laufvariablen vor dem Loop deklarieren wo ich den ganzen Folgecode durchsuche, ob der Wert der Variablen noch irgendwo benutzt wird.

    Wenn das ernsthaft ein relevanter Zeitfaktor ist kann man den Code eh gleich in die Tonne kloppen, weil der Gültigkeitsbereich offensichtlich zu groß ist.

    otze schrieb:

    An so ein Konstrukt gehört ein Kommentar. Mit Begründung(also Benchmark).

    Ich denke sowas ist Allgemeinwissen, ich schreib' ja auch nicht bei jedem std::transform daneben dass die Reihenfolge nicht garantiert ist.

    otze schrieb:

    Und zu dem Zweiten Teil: das hast du natürlich getestet? Ich habe da nicht mehr so viel Vertrauen in meine Compiler.

    Klar habe ich das getestet. 🙂



  • cooky451 schrieb:

    otze schrieb:

    Warum Aua? Dieses Konstrukt ist so unnnatürlich,

    What? 😮 Ich benutze das in etwa 10 mal öfter als das "normale" Konstrukt, und offen gesagt finde ich es wesentlich leichter zu lesen. Ich bevorzuge es auch, wenn Performance garkeine Rolle spielt.

    Das ist relativ untypisch. Was sagen deine Kollegen dazu, für wie lesbar halten sie den Code? Wenn man nicht gerade für sich selbst entwickelt ist Lesbarkeit das oberste Gebot - und da spielen Konventionen eine wichtige Rolle, denen dein Konstrukt nicht so recht entspricht.



  • cooky451 schrieb:

    otze schrieb:

    dass ich erstmal analysiere, ob es überhaupt einen Sinn hat, rückwärts zu iterieren. Das kostet mich im Zweifelsfall, je nach Codekenntnis, 10-30 Minuten, bevor ich daran weiter arbeiten kann.

    Wie lang sind denn deine Schleifen, dass man da 10 Minuten dran lesen kann?

    Man weiß beim unkommentieren Code nicht, ob dieses Konstrukt eine (i.d.R. nutzlose) Performanceoptimierung ist, oder ob die Iteration rückwärts einen semantischen Zweck erfüllt.



  • pumuckl schrieb:

    Das ist relativ untypisch. Was sagen deine Kollegen dazu, für wie lesbar halten sie den Code? Wenn man nicht gerade für sich selbst entwickelt ist Lesbarkeit das oberste Gebot - und da spielen Konventionen eine wichtige Rolle, denen dein Konstrukt nicht so recht entspricht.

    Nicht, dass ich Arbeitskollegen hätte als fast Student, aber die Leute, die ich kenne, hätten damit keine Probleme. Ich verstehe offen gesagt auch nicht so ganz, was daran schwer zu lesen sein soll. Es ist eine Zeile Code, auch wenn ich die noch nie gesehen habe, kann ich in etwa so schnell erfassen was die macht wie ich lesen kann? 😕

    qwertfdassffds schrieb:

    Man weiß beim unkommentieren Code nicht, ob dieses Konstrukt eine (i.d.R. nutzlose) Performanceoptimierung ist, oder ob die Iteration rückwärts einen semantischen Zweck erfüllt.

    Das ist kein Argument dafür per "default" die langsamere Variante zu wählen. Ich weiß bei unkommentiertem Code nicht, ob das vorwärts Iterieren einen semantischen Zweck erfüllt, oder ob man das optimieren kann. Deshalb schreibe ich überall da, wo es einen Zweck erfüllt, ein Kommentar, und überall wo es egal ist, eben halt keinen. Würde anders ja auch wenig Sinn machen.



  • cooky451 schrieb:

    Es ist eine Zeile Code, auch wenn ich die noch nie gesehen habe, kann ich in etwa so schnell erfassen was die macht wie ich lesen kann? 😕

    Nein, eben nicht. Stell dir vor, du bist im Straßenverkehr. Du siehst ein quadratisches, türkisfarbenes Schild an einer Kreuzung, auf dem fett STOP steht. Du weißt, nachdem du es dir angeschaut hast und den Text gelesen hast, was gemeint ist. Wunderst dich aber, warum es nicht das übliche, rote Stop-Schild ist, bei dem du nichtmal mehr richtig liest, sondern in einem Sekundenbruchteil erfasst, welches Schild du vor dir hast (selbst, wenn es auf arabisch ist).

    Das gleiche passiert beim Lesen des Quelltextes. Über das typische for(int i = 0; bla bla...) huscht jeder kurz drüber, ohne die Konzentration auf die wesentlichen Aspekte des Quelltextes zu ignorieren. Bei deinem Rückwärtskonstrukt muss man erstmal anhalten und genau nachlesen, was gemeint ist. Natürlich geht das ziemlich fix, trotzdem stört es den Lesefluss, vor Allem wenn man sich fragt, wo der Haken ist, und warum du nicht die übliche Version genommen hast. Man analysiert das Ganze nochmal und stellt fest, dass es keinen unterschied gibt, trotzdem ist es ein Stolperstein. Man verliert das Wesentliche aus dem Fokus, völlig unnötigerweise. Code, der mit solchen unüblichen Konstrukten durchsetzt ist, liest sich genauso holperig wie ein text, der in einem ungewöhnlichen, unflüssigen Text geschrieben wurde. Man kanns lesen, es ist aber sicher keine Freude.



  • Nun, der Standard ist eben das vorwärts iterieren. Das hat man gelernt, das nutzt man im Regelfall und das ist man auch gewohnt in fremden Codes zu sehen. Findest Du es nicht nachvollziehbar, dass jemand in Deinen Code schaut und sich deswegen überlegt (wieso HIER rückwärts, wieso bricht er mit dem, was man normalerweise macht?). Auch in einer kurzen Schleife mit einer Funktion, die über eine Bildschirmseite gibt, kann das natürlich je nach Aufrufstack-Tiefe und iteriertem Objekt ein komplexer Code sein, über den man dann doch Mal genau nachdenkt.

    Auch wenn Du den Code ohne Performance-Gewinn vorziehen würdest: die meisten schreiben es nicht so, daher ist es erstmal unüblich und somit verwirrender. Und Verwirrung steht guter Lesbarkeit quasi gegenüber.

    Edit: Bis hier hat das pumuckl viel cooler erklärt.

    Und dann würde mich Dein Benchmark-Programm interessieren, das zeigt, dass die Variante performanter ist. Hast Du die zufällig gerade zur Hand?



  • Hmja, aber wenn man's 3 mal gelesen hat ist es genau so drin wie die andere Variante. Und selbst wenn man völlig verwirrt ist und aus dem Konzept kommt verliert man so höchstens 3 mal eine Minute, das ist es definitiv wert.

    @Eisflamme
    Ja, er wäre beim ersten mal verwirrt. Vielleicht beim zweiten mal. Danach ist es aber egal. Ich bin andererseits offen gesagt eher geschockt, dass das nicht alle so machen. Ich muss immer schon schmunzeln wenn ich die andere Variante sehe wo es doch offensichtlich ist, dass es auch rückwärts ginge.

    Ich hab' mal nen relativ aufwendigen Benchmark geschrieben mit Matrizen etc., den finde ich allerdings gerade nicht. Aber ich hab hier mal kurz was zusammengefrickelt. (Hoffentlich ohne Fehler.)

    #include <chrono>
    #include <vector>
    #include <iostream>
    
    template <typename Clock = std::chrono::steady_clock>
    class stopwatch
    {
    	typename Clock::time_point last_;
    
    public:
    	stopwatch()
    		: last_(Clock::now())
    	{}
    
    	void reset()
    	{
    		*this = stopwatch();
    	}
    
    	typename Clock::duration elapsed() const
    	{
    		return Clock::now() - last_;
    	}
    
    	typename Clock::duration tick()
    	{
    		auto now = Clock::now();
    		auto elapsed = now - last_;
    		last_ = now;
    		return elapsed;
    	}
    };
    
    template <typename T, typename Rep, typename Period>
    T duration_cast(const std::chrono::duration<Rep, Period>& duration)
    {
    	return duration.count() * static_cast<T>(Period::num) / static_cast<T>(Period::den);
    }
    
    void foo(std::vector<char>& v)
    {
    	stopwatch<> timer;
    	for (int i = v.size(); i-- != 0; )
    		v[i] = 77;
    	std::cout << "Foo time: " << duration_cast<float>(timer.elapsed()) << '\n';
    }
    
    void bar(std::vector<char>& v)
    {
    	stopwatch<> timer;
    	for (int i = 0; i != v.size(); ++i)
    		v[i] = 44;
    	std::cout << "Bar time: " << duration_cast<float>(timer.elapsed()) << '\n';
    }
    
    int main()
    {
    	std::size_t n;
    	std::cin >> n;
    	std::vector<char> v(n * 1024 * 1024);
    	foo(v);
    	bar(v);
    }
    

    Am besten ein paar mal hintereinander laufen lassen, damit der RAM vorbereitet ist. n zwischen 512 und 1024 sollte gute Ergebnisse bringen.



  • cooky451 schrieb:

    for (auto i = foo(); i-- != 0; )
    

    Wenn foo einen vorzeichenlosen Typen zurückgibt, hast du eine Endlosschleife. So viel zum Thema "gut lesbar".

    Edit: Gilt natürlich nur, wenn du deinen Code erst mal auf das anpasst, was der OP geschrieben hat, nämlich eine Iteration von 0 bis foo() -1 und nicht wie bei dir eine Iteration von 1 bis foo(). Also gleich zwei Fehler in "so einem einfachen Code".



  • Michael E. schrieb:

    cooky451 schrieb:

    for (auto i = foo(); i-- != 0; )
    

    Wenn foo einen vorzeichenlosen Typen zurückgibt, hast du eine Endlosschleife. So viel zum Thema "gut lesbar".

    Falsch. Wird der zweite Satz dadurch richtiger?



  • Michael E. schrieb:

    cooky451 schrieb:

    for (auto i = foo(); i-- != 0; )
    

    Wenn foo einen vorzeichenlosen Typen zurückgibt, hast du eine Endlosschleife. So viel zum Thema "gut lesbar".

    Dein Kommentar zeigt wirklich Mängel in der Lesbarkeit auf, aber anders als Du denkst...



  • cooky451 schrieb:

    Falsch. Wird der zweite Satz dadurch richtiger?

    Siehe Edit:

    Edit: Gilt natürlich nur, wenn du deinen Code erst mal auf das anpasst, was der OP geschrieben hat, nämlich eine Iteration von 0 bis foo() -1 und nicht wie bei dir eine Iteration von 1 bis foo(). Also gleich zwei Fehler in "so einem einfachen Code".



  • Michael E. schrieb:

    Edit: Gilt natürlich nur, wenn du deinen Code erst mal auf das anpasst, was der OP geschrieben hat, nämlich eine Iteration von 0 bis foo() -1 und nicht wie bei dir eine Iteration von 1 bis foo(). Also gleich zwei Fehler in "so einem einfachen Code".

    Langsam wird's lustig. 😃 Aber dafür verstehe ich etwas mehr was pumuckl meint.



  • cooky451 schrieb:

    [..] Wie lang sind denn deine Schleifen, dass man da 10 Minuten dran lesen kann?
    [...] Wenn das ernsthaft ein relevanter Zeitfaktor ist kann man den Code eh gleich in die Tonne kloppen, weil der Gültigkeitsbereich offensichtlich zu groß ist.

    Ich arbeite hier mit zum Teil 15 Jahre alten open source code der von Wissenschaftlern und Studenten geschrieben wurde. Was meinst du, ist die durchschnittliche Codequalität? Natürlich wandert der Code in die Tonne, den neu zu schreiben ist gerade mein Job. Aber dafür muss ich erstmal verstehen was er macht. Und ich habe das Gefühl, dass es so kleine Abweichungen von der Konvention sind, die am meisten Zeit fressen.

    Es gibt ja noch eine andere Konvention, die in einer zeitrelevanten Schleife eingesetzt werden kann:

    std::size_t end = funct();
    for(std::size_t i = 0; i != end; ++i){...}
    

    kostet mich im durchschnitt 2 Sekunden zu verstehen. wenn ich nun das mit deiner Variante vergleiche stellt sich natürlich wiederholt die Frage, ob da nicht doch noch irgendwas passiert, immerhin hättest du die Schlife auch wie oben schreiben können.

    //edit meine Tastatur liegt im persönlichen Krieg mit dem "e"



  • cooky451 schrieb:

    Langsam wird's lustig. 😃 Aber dafür verstehe ich etwas mehr was pumuckl meint.

    Was ist so lustig? Ein typischer realer Ersatz von foo wäre vector::size(). Was passiert hier?

    #include <vector>
    #include <iostream>
    using namespace std;
    
    int main()
    {
            vector<int> foo(2, 0);
            for(auto i = foo.size(); i >= 0; --i)
            {
                    cout << i << endl;
                    if(i > 1000) break;
            }
    }
    

    Ausgabe:

    2
    1
    0
    4294967295
    

Anmelden zum Antworten