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ürstd::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/0a1c1414a68d4a67d4b4Und 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ürstd::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:GCC scheint diesen Punkt zumindest besser hinzubekommen, inlined hier allerdings aus irgendeinem Grund
f
nicht in dercall_noinline
-Funktion und macht 2 Sprünge/Calls: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
...also ein direkter Funktionsaufruf der
std::sort<int*>
-Instanz. Wenn man dasnoinline
herausnimmt, scheint erstd::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.