Kann man irgendwie inlining an der Stelle des Aufrufs unterdrücken?



  • Kann man irgendwie inlining an der Stelle des Aufrufs unterdrücken? Oder geht das nur dort wo die aufgerufene Funktion deklariert/definiert wird?

    Also wenn ich z.B. weiss dass Inlining einer bestimmten Standard-Library Funktion keinen Sinn machen würde, aber diese nicht selbst als "noinline" definiert ist...
    Natürlich kann ich eine Hilfsfunktion schreiben die bloss diese Standard-Library Funktion aufruft, und die Hilfsfunktion dann "noinline" machen.

    Das hat aber u.A. den grossen Nachteil dass es aufwendiger ist. Speziell wenn man damit möglichst effektiv Code-Bloat reduzieren will. z.B. wenn ich in Klasse A und Klasse B jeweils std::sort für std::vector<int> brauche müsste ich die Hilfsfunktion in ein gemeinsames Header-File auslagern, denn sobald ich zwei getrennte Hilfsfunktionen hab bekomme ich den Code potentiell ja wieder doppelt in diese Hilfsfunktionen inlined.

    Mir ist klar dass das wenn überhaupt nur compilerspezifisch gehen wird. Speziell interessant wären für mich MSVC und GCC, aber falls jmd. eine Möglichkeit für einen anderen Compiler kennt würde mich das auch interessieren (auch wenn ich es dann nicht anwenden kann).

    Und falls es wie ich erwarte nicht geht: was haltet ihr von der Idee? Ich denke man könnte das dem GCC/Clang Team ja durchaus mal vorschlagen.



  • ich hab grad nicht viel Zeit, aber kannst du nicht die Optimierung stellenweise verhindern?
    Zum Beispiel mit
    https://gist.github.com/daniel-j-h/0a1c1414a68d4a67d4b4

    Und wenn nötig vllt über ein Funktionszeiger.

    Sind jetzt nur schnelle Ideen, hab gleich ein Termin.



  • Das geht zumindest schonmal mit GCC nicht:

    <source>: In function 'void test(int, int)':
    <source>:17:9: error: #pragma GCC optimize is not allowed inside functions
       17 | #pragma GCC optimize ("0")
          |         ^~~
    

    Und wenn nötig vllt über ein Funktionszeiger.

    Nö, das ganze soll schon auch irgendwie praktikabel sein. Pragma optimize wäre da schon grenzwertig gewesen, aber das hätte man vermutlich noch irgendwie mit Makros hinbiegen können.

    Mal ganz davon abgesehen dass auch nicht jeder Aufruf über einen Funktionszeiger un-optimiert bleibt. Da müsste man schon noch irgendwie tricksen - volatile oder so Scheiss. Und wie gesagt, nicht wirklich praktikabel.

    Ich hätte eher an sowas gedacht:

    #include <vector>
    #include <algorithm>
    
    struct Foo {
        void meh();
        void meh2() {
            m_count++;
        }
    
        bool m_sorted = true;
        std::vector<int> m_vec;
        int m_count = 0;
    };
    
    void Foo::meh() {
        if (__builtin_expect(m_sorted, 0)) // OK, aber änder nix
        [[gnu::cold]] /* funktioniert nicht innerhalb einer Funktion */ {
            std::sort(m_vec.begin(), m_vec.end());
        }
    
        meh2();
    }
    


  • @hustbaer sagte in Kann man irgendwie inlining an der Stelle des Aufrufs unterdrücken?:

    Natürlich kann ich eine Hilfsfunktion schreiben die bloss diese Standard-Library Funktion aufruft, und die Hilfsfunktion dann "noinline" machen.

    Das hat aber u.A. den grossen Nachteil dass es aufwendiger ist. Speziell wenn man damit möglichst effektiv Code-Bloat reduzieren will. z.B. wenn ich in Klasse A und Klasse B jeweils std::sort für std::vector<int> brauche müsste ich die Hilfsfunktion in ein gemeinsames Header-File auslagern, denn sobald ich zwei getrennte Hilfsfunktionen hab bekomme ich den Code potentiell ja wieder doppelt in diese Hilfsfunktionen inlined.

    Ich dachte zuerst an sowas hier - das muss man nicht für jeden Anwendungsfall neu schreiben und kann es einfach in einen Utility-Header auslagern, den man ohnehin fast überall einbindet:

    template <typename F, typename... Args>
    __attribute__((noinline)) inline decltype(auto) call_noinline(F&& f, Args&&... args)
    {
          return std::forward<F>(f)(std::forward<Args>(args)...);
    }
    

    Beim generierten Code fängt z.B. Clang hier allerdings an, die Referenzen allzu wörtlich zu nehmen und selbst bei einem simplen Beispiel einen Umweg über den Stack und Speicheradressen zu nehmen - im Gegensatz zur manuellen f_noinline-Hilfsfunktion:

    https://godbolt.org/z/3rs4Kd

    GCC scheint diesen Punkt zumindest besser hinzubekommen, inlined hier allerdings aus irgendeinem Grund f nicht in der call_noinline-Funktion und macht 2 Sprünge/Calls:

    https://godbolt.org/z/rnqqs3

    Ich gebe dir recht, ein Call Site noinline wäre ein nützliches Feature für speziell dieses Problem. Ich habe das selbst noch keinen Bedarf gehabt, sehe aber den Vorteil beim manuellen Optimieren.

    Ansonsten hab ich noch experimentellen Voodoo anzubieten: Scheinbar akzeptieren GCC und Clang bei einer expliziten Instantiierung ein zusätzliches noinline-Attribut:

    #include <algorithm>
    
    namespace std
    {
        template __attribute__((noinline)) void sort<int*>(int*, int*);
    }
    
    void test(int* a, int* b)
    {
        std::sort(a, b);
    }
    

    Der erzeugte Assembler-Code für test ist bei beiden:

    test(int*, int*): 
            jmp     void std::sort<int*>(int*, int*)     # TAILCALL
    

    https://godbolt.org/z/x1WM86

    ...also ein direkter Funktionsaufruf der std::sort<int*>-Instanz. Wenn man das noinlineherausnimmt, scheint er std::sort wieder zu inlinen:

    test(int*, int*):                            # @test(int*, int*)
            push    r14
            push    rbx
            push    rax
            cmp     rdi, rsi
            je      .LBB0_1
            mov     r14, rsi
            mov     rbx, rdi
            mov     rax, rsi
            sub     rax, rdi
            sar     rax, 2
            bsr     rdx, rax
            xor     rdx, 63
            add     rdx, rdx
            xor     rdx, 126
            call    void std::__introsort_loop<int*, long, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, long, __gnu_cxx::__ops::_Iter_less_iter)
            mov     rdi, rbx
            mov     rsi, r14
            add     rsp, 8
            pop     rbx
            pop     r14
            jmp     void std::__final_insertion_sort<int*, __gnu_cxx::__ops::_Iter_less_iter>(int*, int*, __gnu_cxx::__ops::_Iter_less_iter) # TAILCALL
    

    Das ist vielleicht auch ein Ansatz um bei bestimmten std::sort-Instanzen das Inlining zu unterbinden.



  • @Finnegan
    Interessant, danke.

    Leider nicht das was ich suche, auch nicht wirklich praktikabel.
    Unser Projekt ist zu gross, wir haben viel zu viel Code als dass man da sinnvoll mit expliziten Instanzierungen arbeiten könnte. Da passt die Kosten/Nutzen Rechnung nicht.

    Etwas wie [[gnu::noinline]] was auch direkt beim Aufruf funktioniert und dann nur für diesen einen Aufruf gilt wäre nett.


Log in to reply