Geschwindigkeitsproblem mit großem C-Array unter Verwendung von 64bit Visual C

8

Ich muss eine riesige Menge an Daten in einen Puffer (ca. 20gig) einlesen. Ich habe 192 GB sehr schnelles DDram zur Verfügung, also kein Problem mit der Speichergröße. Ich finde jedoch, dass der folgende Code immer langsamer abläuft, je weiter er in den Puffer gelangt. Der Visual C Profiler sagt mir, dass 68% der 12 Minuten Ausführungszeit in den 2 Anweisungen innerhalb der Schleife in myFunc () ist. Ich bin Win7, 64bit auf einem sehr schnellen Dell mit 2 CPUs, 6 physischen Kernen jeder (24 logische Kerne), und alle 24 Kerne sind völlig ausgereizt, während das läuft.

%Vor%     
PaeneInsula 03.04.2012, 08:20
quelle

4 Antworten

6

Der Code wies 20 Blöcke von einer Milliarde Short-Ints zu. In einem 64-Bit-Windows-Feld ist ein kurzer Int-Wert 2 Byte. Die Zuweisung beträgt also ~ 40 Gigabyte.

Sie sagen, es gibt 24 Kerne und sie sind alle ausgereizt. Der Code, so wie er ist, scheint keine Parallelität zu zeigen. Die Art und Weise, in der der Code parallelisiert wird, könnte sich erheblich auf die Leistung auswirken. Möglicherweise müssen Sie weitere Informationen bereitstellen.

-

Ihr Hauptproblem, vermute ich, dreht sich um Cache-Verhalten und Speicherzugriffsbeschränkungen.

Erstens, mit zwei physischen CPUs mit je sechs Kernen werden Sie Ihren Speicherbus völlig sättigen. Wahrscheinlich haben Sie sowieso eine NUMA-Architektur, aber es gibt keine Kontrolle im Code darüber, wo Ihr calloc () alloziert (z. B. könnte viel Code im Speicher gespeichert sein, für den mehrere Hops benötigt werden).

Hyperthreading ist eingeschaltet. Dies halbiert effektiv die Cache-Größen. Angesichts der Tatsache, dass der Code eher an den Speicherbus gebunden ist als an die Compute gebunden, ist Hyperthreading schädlich. (Das heißt, wenn die Berechnung ständig außerhalb der Cache-Grenzen liegt, ändert sich das nicht viel).

Es ist nicht klar (seit etwas / viel?) Code entfernt, wie auf das Array zugegriffen wird und das Zugriffsmuster und die Optimierung dieses Patterns, um die Cache-Optimierung zu berücksichtigen, ist der Schlüssel zur Performance.

Was ich darin sehe, wie offset () berechnet wird, ist, dass der Code ständig die Erzeugung neuer virtueller zu physischer Adressennachfragen erfordert - von denen jedes so etwas wie vier oder fünf Speicherzugriffe benötigt. Das ist die Leistung von kiling selbst.

Mein grundlegender Ratschlag wäre, das Array in Cachegrößen von Level 2 zu zerlegen, jeder CPU einen Block zu geben und diesen Block verarbeiten zu lassen. Sie können das parallel tun. Eigentlich könnten Sie Hyper-Threading verwenden, um den Cache vorzuladen, aber das ist eine fortgeschrittene Technik.

    
user82238 03.04.2012, 08:51
quelle
2

Sie sollten versuchen, nach Möglichkeit linearer auf das Array zuzugreifen. Dies verursacht wahrscheinlich eine übermäßige Menge an Cache-Fehlern.

    
dbrank0 03.04.2012 08:48
quelle
2

Diese Optimierung wird die langsamen Multiplikationen beseitigen:

%Vor%     
thumbmunkeys 03.04.2012 08:45
quelle
2

Ich denke, das Problem dieses Codes ist sein Speicherzugriffsmuster. Die Tatsache, dass jeder Thread auf Speicher in großen (2 * 800 Byte) Schritten zugreift, hat zwei negative Konsequenzen:

  1. Zu Beginn greifen alle Threads auf den gleichen Speicherbereich zu, der im L2 / L3-Cache vorgeladen ist und von jedem Thread effizient genutzt wird. Später laufen die Threads mit etwas anderer Geschwindigkeit und greifen auf verschiedene Teile des Speichers zu, was zu Cache-Trashing führt (ein Thread lädt Daten in den Cache und entfernt Daten von dort, die noch nicht von anderen Threads gelesen wurden und diese benötigen). Als Ergebnis wird das gleiche Stück Speicher mehrmals in den Cache gelesen (im schlimmsten Fall 12 mal durch die Anzahl der Threads in einer CPU). Da der Speicherbus relativ langsam ist, verlangsamt dies das gesamte Programm.
  2. L1-Cache wird auch nicht sehr effizient verwendet: nur ein kleiner Teil der Daten in jeder Cache-Zeile wird von CPU-Kernen verwendet.

Die Lösung besteht darin, jedem Thread den sequentiellen Zugriff auf den Speicher zu ermöglichen (z. B. den Austausch der Argumente c und d des Makros offSet(a,b,c,d) ).

    
Evgeny Kluev 03.04.2012 11:51
quelle