Caching: volatile, critical section, gar nichts?



  • Dann bau' das ganze mal im Release.



  • Ouch!

    Ok.. da muss ich echt vorsichtiger sein in Zukunft.
    So ähnliche Konstrukte hab ich nämlich schon öfters verwendet, allerdings bis jetzt ohne Probleme..



  • In diesem Zusammenhang habe ich auch noch eine Frage. Und zwar lässt sich folgendes Programm durch Strg+C nicht mehr beenden, wenn ich es mit clang++ und -O3 kompiliere (beim gcc geht’s):

    #include <csignal>
    
    static std::sig_atomic_t g_running = 1;
    
    static void signal_handler(int sig);
    
    int main()
    {
    	std::signal(SIGINT, &signal_handler);
    
    	while (g_running)
    	{
    	}
    }
    
    void signal_handler(int)
    {
    	g_running = 0;
    }
    

    Edit1: Unsinn entfernt.

    Wenn ich hier die Variable volatile mache funktioniert das Programm wie gewünscht unter beiden Compilern.
    Hat mich Tage gekostet einen ähnlichen Fehler zu finden, nur weil ein volatile fehlte 😞
    Was sagt der Standard hierzu?

    Edit2:
    Hab den Übeltäter im Assembler identifiziert:

    4006a0:	a8 01                	test   al,0x1
    4006a2:	b8 00 00 00 00       	mov    eax,0x0
    4006a7:	74 f7                	je     4006a0 <main+0x20>
    

    Kein Wunder, dass hier nix passiert.

    Interessant ist auch, dass das Verhalten hier variiert, wenn ich das selbe Programm in C11 unter verschiedenen Einstellungen kompliere:
    Folgende Tabelle gibt an wie oft ich Strg+C drücken muss um das Programm zu beenden

    | (kein Flag) | -O3 
    ------------------------------
    clang    | 1           | 2
    gcc      | 1           | 2
    musl-gcc | 1           | beendet gar nicht
    


  • der compiler darf optimieren wenn er im scope vom programmfluss sichergehen kann dass es keine seiteneffekte gibt und das ist ja in deinem fall gegeben. das macht der gcc eigentlich auch, vielleicht gibt es da noch mehr flags die du triggern musst. (ich hatte das auf einer console auch mal debuggen duerfen, von daher bin ich mir recht sicher).

    deswegen gibt es das 'volatile' keyword, um dem compiler mitzuteilen, dass das wissen ueber das verhalten dieser variable ausserhalb seines scopes ist.

    wundert mich dass er hlt macht, muesste es nicht einfach ein infinity loop werden? das hatte der gcc bei mir gemacht, quasi

    if(g_running)
            while(true);
    


  • rapso schrieb:

    wundert mich dass er hlt macht, muesste es nicht einfach ein infinity loop werden? das hatte der gcc bei mir gemacht, quasi

    if(g_running)
            while(true);
    

    Jupp, hatte die falsche Stelle im Code als den Übeltäter gehalten, sorry. Der Edit hat’s korrigiert 😉



  • Die Compiler haben recht.

    n3797 schrieb:

    1.9/6
    When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
    — of type volatile std::sig_atomic_t nor
    — lock-free atomic objects (29.4)
    are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.



  • camper schrieb:

    Die Compiler haben recht.

    n3797 schrieb:

    1.9/6
    When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
    — of type volatile std::sig_atomic_t nor
    — lock-free atomic objects (29.4)
    are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.

    g_running ist aber std::sig_atomic_t.



  • Nathan schrieb:

    camper schrieb:

    Die Compiler haben recht.

    n3797 schrieb:

    1.9/6
    When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
    — of type volatile std::sig_atomic_t nor
    — lock-free atomic objects (29.4)
    are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.

    g_running ist aber std::sig_atomic_t.

    aber eben nicht volatile



  • Der Einfachkeit halber würde ich std::sig_atomic_t ganz aus dem Gedächtnis streichen und nur mehr std::atomic verwenden.
    Vor allem weil der std::sig_atomic_t und std::atomic so klingen als hätten sie was gemeinsam. Also wenn man std::atomic kennt und dann std::sig_atomic_t liest könnte man annehmen "passt eh alles".

    Tut es aber, ohne volatile , wie man hier ja schön sehen konnte, eben nicht 😉

    (Und auch mit volatile sollte man vorsichtig sein - gibt z.B. Umgebungen wo Signals immer in nem eigenen Thread ausgeführt werden, und wenn diese dann bei volatile keine C#-volatile-ähnliches Verhalten implementieren, dann könnte es Probleme geben.)



  • hustbaer schrieb:

    Der Einfachkeit halber würde ich std::sig_atomic_t ganz aus dem Gedächtnis streichen und nur mehr std::atomic verwenden.

    wenn man alles was probleme bereiten kann der einfachheit halber laesst, koennte es am ende sperrlich mit den skills aussehen.

    ich wuerde vorschlagen sich mit den dingen die man nicht kann auseinander zu setzen bis man sie kann. als programmierer sollte man bereit sein das ganze leben lang dazu zu lernen. my2cent.



  • @rapso
    Verstehe dich nicht.
    Wieso sollte man std::sig_atomic_t noch verwenden?
    Was kann man damit machen was man mit std::atomic nicht genau so gut oder besser machen kann? 😕
    (Vorausgesetzt natürlich man hat std::atomic zur Verfügung.)

    rapso schrieb:

    ich wuerde vorschlagen sich mit den dingen die man nicht kann auseinander zu setzen bis man sie kann. als programmierer sollte man bereit sein das ganze leben lang dazu zu lernen. my2cent.

    Und genau das mache ich hier.

    Ich weiss echt nicht was du willst.
    Und vor allem was der mMn. unangebrachte "dazulernen" Kommentar soll.



  • hustbaer schrieb:

    @rapso
    Verstehe dich nicht.
    Wieso sollte man std::sig_atomic_t noch verwenden?
    Was kann man damit machen was man mit std::atomic nicht genau so gut oder besser machen kann? 😕

    ich nahm an du kennst den unterschied wenn du den tipp gibst und sagst nur 'der einfachheit halber' .

    also, sowas einfaches wie z.b.

    while(g_running)
    {
    }
    g_running=1;
    

    sind schnelle operationen, du liest den cache, du schreibst den cache. wenn du hingegen std::atomic verwendest hast du dafuer immer atomic operationen (und bei std::atomic sind auch die operatoren so ueberladen), und dann gehst du ueber den memory controller, je nach system kann das 200mal langsammer sein.

    dabei bremst du nicht nur den loop aus, je nach protokol dass fuer die synchronisierung verwendet wird, kannst du andere threads verlangsammen.

    besonders wenn du contention hast, z.b. weil in einem job system mehrere threads auf ein flag pollen, kann das den bus/controller ziemlich auslasten.

    rapso schrieb:

    ich wuerde vorschlagen sich mit den dingen die man nicht kann auseinander zu setzen bis man sie kann. als programmierer sollte man bereit sein das ganze leben lang dazu zu lernen. my2cent.

    Und genau das mache ich hier.

    Und vor allem was der mMn. unangebrachte "dazulernen" Kommentar soll.

    ich finde dass du das hier nicht machst, jedenfalls nicht sonderlich gut. dein vorschlag "nimm immer nur std::atomic" und die begruendung ist "der einfachheit halber". Ich finde das ist echt kein gutes argument, stell dir vor er soll jemandem anderen erklaeren weshalb er das verwendet "ist halt einfacher, ich weiss nicht, ob es schlechter oder besser ist".

    und damit es nicht langweilig und trocken bleibt, hier ein (poor man's) test source:

    #include <atomic>
    #include <csignal>
    #include <Windows.h>
    
    int main(int argc,char* argv[])
    {
    	const size_t T0=GetTickCount();
    	for(std::atomic<int> a=0;a<~0u;a++)
    	{
    	}
    	const size_t T1=GetTickCount();
    	for(volatile std::sig_atomic_t a=0;a<~0u;a++)
    	{
    	}
    	const size_t T2=GetTickCount();
    	for(int a=0;a<~0u;a++)
    	{
    	}
    	const size_t T3=GetTickCount();
    	printf("Test:%d %d %d\n",T1-T0,T2-T1,T3-T2);
    }
    

    release build, VS2012, auf einem i7-4770k:

    Test:59140 7098 0
    

    wenn wir von 3.5GHz ausgehen und jeweils ein add,compare,conditional jump annehmen, sind das
    59140->16 cycle/instruction
    7098->1.9cycle/instruction
    0->wegoptimiert, wie zu erwarten war, sanity check, dass es release build ist 🙂

    Ich weiss echt nicht was du willst.

    sorry wenn das wie ein angriff oder denunzierend klang, das war wertneutral gemeint und nicht gegen dich persoenlich gerichtet. du bist einer der netteren hier, hab kein beduerfniss dich grundlos zum feind zu machen 😉



  • rapso schrieb:

    for(int a=0;a<~0u;a++)
    	{
    	}
    }
    

    Bei einem Vergleich zwischen int und unsigned wird nach unsigned konvertiert und dann verglichen, richtig?
    std::numeric_limits<int>::max() ist auf jedem System, das ich kenne, kleiner als ~0u .
    a läuft über, also hast du da undefiniertes Verhalten.
    Du misst also potentiell Mist.

    Außerdem hat deine Variante mit atomic zwei atomare Operationen ( < und ++ ), obwohl das mit nur einer möglich wäre ( fetch_add , glaube ich).



  • TyRoXx schrieb:

    ...

    ok, du hast mich da erwischt, ich gebe es zu, es ist poor man's code. verflixt.

    Außerdem hat deine Variante mit atomic zwei atomare Operationen ( < und ++ ),

    jup, das schrieb ich ja weiter oben, jegliche operation ist atomar, selbst wenn du es nicht brauchst.

    obwohl das mit nur einer möglich wäre ( fetch_add , glaube ich).

    ist es nicht ein wenig sinnfrei ein beispiel zu optimieren? der naechste kommt und sagt, dass man die variable auch in eine nicht atomic stecken koennte, und am ende wieder zuweist. der naechste sagt dann dass man alles auskommentieren koennte weil das beispiel nichts nuetzliches macht. und ja, ihr habt alle recht, aber das ist doch am punkt vorbei, zu demonstrieren, dass atomics sogar im einfachsten fall kosten, und zwar nicht gerade wenig. ich finde praktische beispiele nett, damit wir wissen worueber wir reden. ich haette auch meine "200mal langsammer" stehen lassen koennen ohne jegliches beispiel. dann glaubst du mir entweder oder denkst ich bin ein idiot, aber auf jeden fall hast du keine erfahrung gewonnen dein wissen zu untermauern.


Anmelden zum Antworten