{"id":3601,"date":"2018-07-21T15:29:13","date_gmt":"2018-07-21T14:29:13","guid":{"rendered":"http:\/\/roboblog.fatal-fury.de\/?p=3601"},"modified":"2018-07-21T15:29:13","modified_gmt":"2018-07-21T14:29:13","slug":"c-guns-praxisbeispiel-objekt-v-s-funktional","status":"publish","type":"post","link":"http:\/\/roboblog.fatal-fury.de\/?p=3601","title":{"rendered":"C+ Guns: Praxisbeispiel: Objekt v.s. Funktional"},"content":{"rendered":"<p>Objekt v.s. Funktional<br \/>\nKeine Angst, ich will hier keinen Glaubenskrieg ausl\u00f6sen. Mich interessieren m\u00f6gliche Performance Nachteile und wie sie entstehen.<\/p>\n<p>Das Beispiel zeigt einen vereinfachten Codeausschnitt aus der Praxis, bei dem jede Variable als Funktionsargument \u00fcbergeben wird. Meine Frage ist nun, ob dies Performancetechnich schlechter ist, als alle Funktionen als Memberfunktionen auszulegen und alle Variablen als Membervariablen.<\/p>\n<blockquote><p>\nFazit:<br \/>\nF\u00fcr das gew\u00e4hlte Beispiel mit drei Funktionsargumenten entsteht kein nennenswerter Performance Unterschied. Die Lesbarkeit des Code, und damit die Fehlerfreiheit und Wartbarkeit des Codes, leidet allerdings stark wenn jede ben\u00f6tigte Variable als Funktionsargument \u00fcbergeben wird. Die Methode mit Memberfunktionen l\u00f6st nicht alle Probleme. Die beste Kombination der Paradigmen von C++ habe ich noch nicht gefunden.<\/p><\/blockquote>\n<p>Hier der Kern des Programms ohne Memberfunktionen.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nextern double calcT(const double Tmax, const vector&lt;Vec3&gt;&amp; U, const M&amp; m, bool&amp; finish);\r\nextern void calcF(const vector&lt;Vec3&gt;&amp; U, const M&amp;m, vector&lt;Vec3&gt;&amp; F);\r\nextern void calcE(const vector&lt;Vec3&gt;&amp; U, int nE, const vector&lt;E_t&gt;&amp; E, const M&amp; m);\r\nextern void calcQ(const vector&lt;Vec3&gt;&amp; U, int nE, const vector&lt;E_t&gt;&amp; E, const M&amp; m, const vector&lt;Vec2&gt;&amp; iN, const vector&lt;int&gt;&amp; NCO, const vector&lt;int&gt;&amp; CO, vector&lt;Vec3&gt;&amp; Q);\r\nextern void calcI(vector&lt;Vec3&gt;&amp; U, double T, const vector&lt;Vec3&gt;&amp; F, const M&amp; m, const vector&lt;Vec3&gt;&amp; Q);\r\nextern void calcC(const vector&lt;Vec3&gt;&amp; U, vector&lt;Vec2&gt;&amp; C);\r\n\r\nauto exec(double Tmax, const M&amp; m, const vector&lt;int&gt;&amp; NCO, const vector&lt;int&gt;&amp; CO,\r\n    const int nE, const vector&lt;E_t&gt;&amp; E, const vector&lt;Vec2&gt;&amp; iN,\r\n    vector&lt;Vec3&gt;&amp; U, vector&lt;Vec2&gt;&amp; C, vector&lt;Vec3&gt;&amp; F, vector&lt;Vec3&gt;&amp; Q) \r\n{\r\n    double Tinc = 0;\r\n    while(Tinc &lt; Tmax) {\r\n        double T = calcT(Tmax, U, m);\r\n        Tinc += T;\r\n        calcF(U, m, F);\r\n        calcE(U, nE, E, m);        \r\n        calcQ(U, nE, E, m, iN, NCO, CO, Q);\r\n        calcI(U, T, F, m, Q);\r\n        calcC(U, C);\r\n    }\r\n}\r\n\r\nauto func() {\r\n    double Tmax;\r\n    M m;\r\n    vector&lt;Vec3&gt; U;\r\n    vector&lt;Vec3&gt; F;\r\n    int nE;\r\n    vector&lt;E_t&gt; E;\r\n    vector&lt;Vec2&gt; iN;\r\n    vector&lt;Vec3&gt; Q;\r\n    vector&lt;Vec2&gt; C;\r\n    vector&lt;int&gt; NCO;\r\n    vector&lt;int&gt; CO;\r\n    double T;\r\n\r\n    init(...);\r\n    exec(Tmax, m, NCO, CO, nE, E, iN, U, C, F, Q);\r\n}\r\n<\/pre>\n<p>Es gibt eine Reihe von Funktionen die nacheinander mit unterschiedlichen Variablen aufgerufen werden und teilweise die \u00fcbergebenen Argumente ver\u00e4ndern. Die Funktions- und Variablennamen sind schon stark gek\u00fcrzt, einerseits um das Beispiel einfach zu halten, andererseits ist die Lesbarkeit durch die \u00dcbergabe jeder einzelnen Variable schon beeintr\u00e4chtigt. Die Funktionen selbst sind als \"extern\" ausgelegt, da zu diesem Zeitpunkt nicht die genaue Implementierung interessiert. Weiterhin sei angenommen, dass die Funktionen selbst alle gro\u00df und komplex sind, so dass der Compiler den Funktionsaufruf nicht durch inline eliminiert.<\/p>\n<p>Es gibt eine ganze Reihe von Nachteilen. Ich will sie nicht im Detail erl\u00e4utern, nur kurz aufz\u00e4hlen:<br \/>\n- Erstes nat\u00fcrlich die redselige Form des Codes. Es wird oft an vielen Stellen immer der selbe Code wiederholt.<br \/>\n- Mit aussagekr\u00e4ftigen Variablen Namen wird der Code nur noch schlechter lesbar. Das ist ein Widerspruch. Der Grund hierf\u00fcr ist sind die Funktionsdeklaration, die nun sehr lang werden, oder sich \u00fcber mehrere Zeilen erstreckt.<br \/>\n- Es ist zwar ersichtlich, welche Funktion von welcher Variablen abh\u00e4ngt, aber nicht welche Variablen ge\u00e4ndert werden. Nat\u00fcrlich kann man eine Konvention eingehen und nicht konstanten Argumente an das Ende der Argumente legen, aber das ist keine Garantie.<br \/>\n- Die Sache dass eine Funktion ihr Ergebnis als R\u00fcckgabewert liefert ist auch nicht so einfach. Die Ergebnisse sind gro\u00dfe Arrays die auf keinen Fall zur Rechenzeit neu allokiert werden d\u00fcrfen. Wie kann man das garantieren?<br \/>\n- Auch k\u00f6nnen die Argumente leicht verwechselt werden. Die Variablen F und Q haben den selben Typ. Nat\u00fcrlich l\u00e4sst sich das Problem durch explizite Typen wie 'F_t' und 'Q_t' l\u00f6sen, aber das erh\u00f6ht wieder den Codebedarf.<\/p>\n<p>Die Variante mit Memberfunktionen l\u00f6st einige Probleme. Hier zum Vergleich der selbe Code etwas umgeschrieben:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct Methode1 {\r\n    const double Tmax;\r\n    const M m;\r\n    vector&lt;Vec3&gt; U;\r\n    vector&lt;Vec3&gt; F;\r\n    const int nE;\r\n    vector&lt;E_t&gt; E;\r\n    const vector&lt;Vec2&gt; iN;\r\n    vector&lt;Vec3&gt; Q;\r\n    vector&lt;Vec2&gt; C;\r\n    const vector&lt;int&gt; NCO;\r\n    const vector&lt;int&gt; CO;\r\n\r\n    void init() { ... };\r\n\r\n    double calcT(bool&amp; finish);\r\n    void calcF();\r\n    void calcE();\r\n    void calcQ();\r\n    void calcI();\r\n    void calcC();\r\n\r\n    auto exec() {\r\n        double Tinc = 0,\r\n        while(Tinc &lt; Tmax) {\r\n            double T = calcT();\r\n            Tinc += T;\r\n            calcF();\r\n            calcE();        \r\n            calcQ();\r\n            calcI();\r\n            calcC();\r\n        }\r\n    }\r\n};\r\n<\/pre>\n<p>Viel redundanter Code f\u00e4llt weg. Allerdings ist auch nicht mehr ersichtlich welche Funktion von welcher Variablen abh\u00e4ngt oder sie \u00e4ndert. Darauf gehen ich im n\u00e4chsten Post ein. Momentan interessiert die Performance. Dazu schauen wir uns am besten den Assemblercode an. F\u00fcr alle die kein Assembler lesen k\u00f6nnen, habe ich den Code etwas dokumentiert.<\/p>\n<p>Hier ein Ausschnitt der Funktion \"exec\" vom ersten Code Beispiel:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n.L2:                                         # Begin Schleife\r\n        movq    %rbp, %rsi                   # copy address of variable m to rsi\r\n        movq    %rbx, %rdi                   # copy address of variable U to rdi\r\n        movsd   16(%rsp), %xmm0              # copy Tmax to xmm0\r\n        call    calcT(Tmax, U, m)\r\n        movsd   8(%rsp), %xmm1               # copy Tinc, 8(rsp) to xmm1\r\n        movsd   %xmm0, 24(%rsp)              # copy result of calcT to 24(rsp) for later use\r\n        addsd   %xmm0, %xmm1                 # Tinc = Tinc + T\r\n        movsd   %xmm1, 8(%rsp)               # copy Tinc back to 8(rsp)\r\n\r\n        movq    %r13, %rdx                   # F\r\n        movq    %rbp, %rsi                   # m\r\n        movq    %rbx, %rdi                   # U\r\n        call    calcF(U, m, F)\r\n\r\n        movq    %rbp, %rcx                   # m \r\n        movq    %r14, %rdx                   # E\r\n        movl    %r15d, %esi                  # nE\r\n        movq    %rbx, %rdi                   # U\r\n        call    calcE(U, nE, E, m)\r\n\r\n        pushq   %r12                         # Q  is passed per stack \r\n        pushq   48(%rsp)                     # CO is passed per stack\r\n        movq    48(%rsp), %r9                # NCO\r\n        movq    128(%rsp), %r8               # iN \r\n        movq    %rbp, %rcx                   # m\r\n        movq    %r14, %rdx                   # E\r\n        movl    %r15d, %esi                  # nE\r\n        movq    %rbx, %rdi                   # U\r\n        call    calcQ(U, nE, E, m, iN, NCO, CO, Q)\r\n\r\n        addq    $16, %rsp                    # add 16 to rsp to adjust stack pointer\r\n        movq    %r12, %rcx                   # Q\r\n        movq    %rbp, %rdx                   # m\r\n        movq    %r13, %rsi                   # F\r\n        movsd   24(%rsp), %xmm0              # T\r\n        movq    %rbx, %rdi                   # U\r\n        call    calcI(U, T, F, m, Q)\r\n\r\n        movq    128(%rsp), %rsi              # C\r\n        movq    %rbx, %rdi                   # U\r\n        call    calcC(U, C)\r\n\r\n        movsd   16(%rsp), %xmm2              # note: rsp has changed. so the offset to T and Tinc is different\r\n        comisd  8(%rsp), %xmm2               # Tinc &lt; Tmax ?\r\n        ja      .L4                          # jump to loop head\r\n<\/pre>\n<p>Vor jedem Funktionsaufruf werden die Adressen bzw. Werte der Argumente in ein Register kopiert. Das sind auf der Aufrufer Seite mindestens so viele Instruktionen wie Funktionsargumente. In der aufgerufenen Funktion selbst gibt es wohl auch noch zus\u00e4tzliche Instruktionen. Genau um diesen Mehraufwand geht es mir. Ich vermute nun, dass dieser Aufwand eingespart werden kann, ohne die Funktion zu inlinen.<br \/>\nDazu m\u00fcssen wir allerdings genauer in den Code rein schauen.<\/p>\n<p>Beispielsweise die einfache Funktion calcT:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ndouble calcT(const double Tmax, const vector&lt;Vec3&gt;&amp; U, const M&amp; m) {\r\n    double T_min = Tmax;\r\n    for(int IP=0; IP &lt; m.MNP; ++IP) {\r\n        double T = m.A&#x5B;IP]\/(U&#x5B;IP]&#x5B;1]\/U&#x5B;IP]&#x5B;0]+U&#x5B;IP]&#x5B;2]\/U&#x5B;IP]&#x5B;0]);\r\n        if(T &lt; T_min) {\r\n            T_min = T;        \r\n        }\r\n    }\r\n    return T_min;\r\n}\r\n<\/pre>\n<p>Im Prinzip wird hier nur ein Minimum gesucht. Wie genau sich das Berechnet ist egal. Zur Erinnerung: Die vier Argumente 'Tmax', 'U' und 'm' der Funktion wurden per Register xmm0, rdi, rsi \u00fcbergeben. Das Ergebnis per Register xmm0 zur\u00fcck gegeben.<\/p>\n<p>Auf den Assemblercode will ich nicht im Detail eingehen. Den kann jeder f\u00fcr sich selbst einmal durch kauen. Wichtig ist der Unterschied zur selben Funktion nur als Member Funktion implementiert.<\/p>\n<table>\n<tr>\n<td>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ncalcT(Tmax, U, m):\r\n        movl    (%rsi), %ecx\r\n        testl   %ecx, %ecx\r\n        jle     .L1\r\n        movq    8(%rsi), %rdx\r\n        movq    (%rdi), %rax\r\n        leal    -1(%rcx), %ecx\r\n        leaq    8(%rdx,%rcx,8), %rcx\r\n.L5:\r\n        movsd   (%rax), %xmm3\r\n        movsd   8(%rax), %xmm1\r\n        divsd   %xmm3, %xmm1\r\n        movsd   16(%rax), %xmm2\r\n        divsd   %xmm3, %xmm2\r\n        addsd   %xmm2, %xmm1\r\n        movsd   (%rdx), %xmm2\r\n        divsd   %xmm1, %xmm2\r\n        movapd  %xmm2, %xmm1\r\n        minsd   %xmm0, %xmm1\r\n        movapd  %xmm1, %xmm0\r\n        addq    $8, %rdx\r\n        addq    $24, %rax\r\n        cmpq    %rcx, %rdx\r\n        jne     .L5\r\n.L1:\r\n        ret\r\n\r\nexec():\r\n.L12:                          # Loop head\r\n  movq    %rbp, %rsi\r\n  movq    %rbx, %rdi\r\n  movsd   16(%rsp), %xmm0      # copy Tmax to xmm0\r\n  call    calcT(Tmax, U, m)    # result T is stored in xmm0\r\n  movsd   8(%rsp), %xmm1       # copy T to xmm1 for later use\r\n  movsd   %xmm0, 24(%rsp)      # copy T to 24(rsp)\r\n  addsd   %xmm0, %xmm1         # Tinc += T\r\n  movsd   %xmm1, 8(%rsp)       # copy Tinc back to 8(rsp)\r\n...\r\n  movsd   16(%rsp), %xmm2      # copy Tmax to xmm2\r\n  comisd  8(%rsp), %xmm2       # Tinc &lt; Tmax ?\r\n  ja      .L12                 # jump to loop head\r\n\r\n<\/pre>\n<\/td>\n<td>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nMethode1::calcT():\r\n        movsd   (%rdi), %xmm0       #  copy Tmax to xmm0\r\n        movl    8(%rdi), %ecx\r\n        testl   %ecx, %ecx\r\n        jle     .L22\r\n        movq    16(%rdi), %rdx\r\n        movq    40(%rdi), %rax\r\n        leal    -1(%rcx), %ecx\r\n        leaq    8(%rdx,%rcx,8), %rcx\r\n.L26:\r\n        movsd   (%rax), %xmm3\r\n        movsd   8(%rax), %xmm1\r\n        divsd   %xmm3, %xmm1\r\n        movsd   16(%rax), %xmm2\r\n        divsd   %xmm3, %xmm2\r\n        addsd   %xmm2, %xmm1\r\n        movsd   (%rdx), %xmm2\r\n        divsd   %xmm1, %xmm2\r\n        movapd  %xmm2, %xmm1\r\n        minsd   %xmm0, %xmm1\r\n        movapd  %xmm1, %xmm0\r\n        addq    $8, %rdx\r\n        addq    $24, %rax\r\n        cmpq    %rcx, %rdx\r\n        jne     .L26\r\n.L22:\r\n        ret\r\n\r\nMethode1::exec():\r\n.L42:                       # Loop head\r\n  movq    %rbx, %rdi        # copy address of this objekt to rdi. Its the same address of Tmax\r\n  call    Methode1::calcT() # result T is stored in xmm0\r\n  movsd   (%rsp), %xmm1     # copy Tinc to xmm1\r\n  movsd   %xmm0, 8(%rsp)    # copy T to 8(rsp) for later use\r\n  addsd   %xmm0, %xmm1      # Tinc += T\r\n  movsd   %xmm1, (%rsp)     # copy Tinc to rsp\r\n...\r\n  movsd   (%rbx), %xmm0     # Copy Tmax to xmm0\r\n  comisd  (%rsp), %xmm0     # Tinc &lt; Tmax\r\n  ja      .L42              # Jump to loop head\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>Nun, es gibt f\u00fcr dieses Beispiel praktisch keinen Unterschied. Die Variante mit Member Funktionen ist eine Zeile k\u00fcrzer. Woran liegt das? Statt drei Funktionsargumente zu \u00fcbergeben muss nur eines \u00fcbergeben werden, die Adresse von 'this'. Da die Funktion ja nicht geinlint wurde, ist das notwendig. Und zuf\u00e4llig ist bei diesem Algorithmus bei der ersten Variante das erste Funktionsargument gleichzeitig der R\u00fcckgabewert, wenn die Schleife nicht l\u00e4uft. Dieser Schritt mit dennoch bei der zweiten Variante ausgef\u00fchrt werden. So bleibt nur eine Zeile \u00fcbrig die eingespart wird.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Objekt v.s. Funktional Keine Angst, ich will hier keinen Glaubenskrieg ausl\u00f6sen. Mich interessieren m\u00f6gliche Performance Nachteile und wie sie entstehen. Das Beispiel zeigt einen vereinfachten Codeausschnitt aus der Praxis, bei dem jede Variable als Funktionsargument \u00fcbergeben wird. Meine Frage ist nun, ob dies Performancetechnich schlechter ist, als alle Funktionen als Memberfunktionen auszulegen und alle Variablen [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[17],"class_list":["post-3601","post","type-post","status-publish","format-standard","hentry","category-allgemein","tag-cpp"],"_links":{"self":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/3601","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3601"}],"version-history":[{"count":13,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/3601\/revisions"}],"predecessor-version":[{"id":3614,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/3601\/revisions\/3614"}],"wp:attachment":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3601"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3601"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3601"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}