Ist es normal, dass die gcc-Atom-Builtins so langsam sind?

7

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:

  • einfaches Post-Inkrement: 431 Nanosekunden
  • atomischer Abruf- und Addiervorgang: 14882393 Nanosekunden

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:

%Vor%     
Brady 23.07.2012, 08:27
quelle

3 Antworten

18

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.

    
interjay 23.07.2012, 08:40
quelle
6

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!

    
Martin B 23.07.2012 08:40
quelle
1

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.

    
Puppy 23.07.2012 09:35
quelle

Tags und Links