Ich habe eine Anwendung, bei der ich einige Statistikzähler in einer Multithread-Methode inkrementieren muss. Das Inkrementieren muss thread-sicher sein, also entschied ich mich, die gcc atomic builtins __sync_add_and_fetch()
Funktion zu verwenden. Um eine Vorstellung von deren Auswirkungen zu bekommen, habe ich ein paar einfache Leistungstests durchgeführt und festgestellt, dass diese Funktionen viel langsamer sind als einfache Pre- / Post-Inkrementierungen.
Hier ist das Testprogramm, das ich erstellt habe:
%Vor%Und hier sind die Ergebnisse:
%Vor%Hier ist die gcc-Zusammenstellung:
%Vor%Und verwandte Informationen und Versionen:
%Vor%Und nun zur eigentlichen Frage :) Ist es normal, dass die atomaren Operationen so viel langsamer sind?
Der Unterschied für eine Million Iterationen ist:
Natürlich verstehe ich, dass eine atomare Operation teurer sein sollte, aber das scheint übertrieben. Nur der Vollständigkeit halber habe ich auch einen Pthread-Mutex und -Rwlock überprüft. Zumindest sind die atomaren Operationen schneller als die Pthread-Operationen, aber ich frage mich immer noch, ob ich etwas falsch gemacht habe. Ich konnte es nicht verknüpfen, ohne die -march=i686
-Option anzugeben, vielleicht hat das eine Auswirkung?
UPDATE:
Ich habe die -O2
Compiler-Optimierung herausgenommen und konnte folgende kohärentere Ergebnisse erzielen:
Die Antwort ist, dass GCC Ihre nicht-atomaren Schritte entfernt optimiert. Wenn es eine Schleife wie folgt sieht:
%Vor%ersetzt es durch:
%Vor%Dies ist in der generierten Assembly zu sehen, die Folgendes enthält:
%Vor%Sie messen also nicht, was Sie für Sie halten.
Sie können Ihre Variable volatile
erstellen, um diese Optimierung zu verhindern. Auf meinem Computer ist der nicht-atomare Zugriff ungefähr achtmal so schnell wie der atomare Zugriff. Bei Verwendung einer 32-Bit-Variablen anstelle von 64-Bit (ich kompiliere als 32-Bit), fällt die Differenz auf etwa einen Faktor von 3.
Ich vermute, dass gcc Ihre nicht-atomare Inkrement-Operation auf etwas wie
optimiert %Vor%Sie sagen, dass 10 ^ 6 Inkremente 431 Nanosekunden benötigen, was zu 0,000431 ns pro Schleifeniteration führt. Bei einem 4-GHz-Prozessor beträgt der Taktzyklus 0,25 ns, also ist es ziemlich offensichtlich, dass die Schleife weg optimiert wird. Dies erklärt den großen Leistungsunterschied, den Sie sehen.
Edit: Sie haben eine atomare Operation mit 14 ns gemessen - bei einem 4 GHz-Prozessor, was 56 Zyklen ergibt, was ziemlich gut ist!
Die Langsamkeit eines Synchronisationsmechanismus kann nicht durch einen einzigen Thread gemessen werden. Single-Prozess-Sync-Objekte wie POSIX Mutexe / Windows kritische Abschnitte kosten nur dann wirklich Zeit, wenn sie umkämpft sind.
Sie müssten mehrere Threads einführen - andere Arbeiten, die die Zeit Ihrer realen Anwendung widerspiegeln - für die synchronisierten Methoden, um eine Vorstellung davon zu bekommen, wie lange es dauert.
Tags und Links c++ gcc performance atomic built-in