Ich experimentierte mit AVX-AVX2-Befehlssätzen, um die Leistung des Streamens auf aufeinanderfolgenden Arrays zu sehen. Also habe ich unten ein Beispiel, wo ich Grundspeicher lese und speicher.Haswell-Speicherzugriff
#include <iostream>
#include <string.h>
#include <immintrin.h>
#include <chrono>
const uint64_t BENCHMARK_SIZE = 5000;
typedef struct alignas(32) data_t {
double a[BENCHMARK_SIZE];
double c[BENCHMARK_SIZE];
alignas(32) double b[BENCHMARK_SIZE];
}
data;
int main() {
data myData;
memset(&myData, 0, sizeof(data_t));
auto start = std::chrono::high_resolution_clock::now();
for (auto i = 0; i < std::micro::den; i++) {
for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
myData.b[i] = myData.a[i] + 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << (end - start).count()/std::micro::den << " " << myData.b[1]
<< std::endl;
}
Und nachdem er mit Kompilieren g ++ - 4.9 -ggdb -march = Kern-AVX2 -std = C++ 11 struct_of_arrays.cpp O3 -o struct_of_arrays
I pro Zyklus Leistung recht guten Unterricht zu sehen und Timings, für Benchmark-Größe 4000. Aber sobald ich die Benchmark-Größe auf 5000 erhöhen, sehe ich, dass der Befehl pro Zyklus deutlich sinkt und auch Latenzsprünge. Jetzt ist meine Frage, obwohl ich sehen kann, dass Leistungseinbußen scheint mit L1-Cache verwandt zu sein, kann ich nicht erklären, warum dies so plötzlich passiert.
Um mehr Einblick zu geben, wenn ich mit Benchmark Größe 4000 perf laufen und 5000
| Event | Size=4000 | Size=5000 |
|-------------------------------------+-----------+-----------|
| Time | 245 ns | 950 ns |
| L1 load hit | 525881 | 527210 |
| L1 Load miss | 16689 | 21331 |
| L1D writebacks that access L2 cache | 1172328 | 623710387 |
| L1D Data line replacements | 1423213 | 624753092 |
So ist meine Frage, warum diese Auswirkungen geschehen, haswell Erwägung ziehen, soll auf der Bereitstellung von 2 * 32 Byte der Lage sein, lesen, und 32 Bytes speichern jeden Zyklus?
EDIT 1
ich mit diesem Code gcc realisiert intelligent beseitigt Zugriffe auf die myData.a, da es auf 0 gesetzt ist dies zu vermeiden Ich habe eine andere Benchmark, die etwas anders ist, wo ein explizit gesetzt .
Im zweiten Beispiel wird ein Array gelesen und ein anderes Array geschrieben. Und dieser produziert für verschiedene Größen perf Ausgabe folgende:
| Event | Size=1000 | Size=2000 | Size=3000 | Size=4000 |
|----------------+-------------+-------------+-------------+---------------|
| Time | 86 ns | 166 ns | 734 ns | 931 ns |
| L1 load hit | 252,807,410 | 494,765,803 | 9,335,692 | 9,878,121 |
| L1 load miss | 24,931 | 585,891 | 370,834,983 | 495,678,895 |
| L2 load hit | 16,274 | 361,196 | 371,128,643 | 495,554,002 |
| L2 load miss | 9,589 | 11,586 | 18,240 | 40,147 |
| L1D wb acc. L2 | 9,121 | 771,073 | 374,957,848 | 500,066,160 |
| L1D repl. | 19,335 | 1,834,100 | 751,189,826 | 1,000,053,544 |
wieder gleiche Muster in der Antwort wie erwähnt zu sehen sind, mit Datensatz Größendaten zu erhöhen paßt nicht mehr in L1 und L2 wird Engpass. Was ist auch interessant ist, dass Prefetching scheint nicht zu helfen und L1 vermisst erhöht erheblich. Obwohl ich erwarte, eine Trefferrate von mindestens 50 Prozent zu sehen, wenn man bedenkt, dass jede in L1 zum Lesen eingelesene Cachezeile ein Treffer für den zweiten Zugriff ist (64 Byte Cachezeile 32 Byte wird mit jeder Iteration gelesen). Sobald der Datensatz jedoch auf L2 übertragen wurde, scheint die L1-Trefferrate auf 2% gefallen zu sein. Wenn man bedenkt, dass Arrays sich nicht wirklich mit der L1-Cache-Größe überschneiden, sollte dies nicht an Cache-Konflikten liegen. Also macht dieser Teil immer noch keinen Sinn für mich.
+1. Das einzige, was ich hinzufügen würde ist, dass auf jeder x86-Plattform, die ich gesehen habe, ein Double ist 8 Bytes. –
In der Tat haben Sie Recht mit Rückschreiben und wie sie Bandbreite verbrauchen, wenn sie nicht in L1 sind. Es ist enttäuschend, die Leistungsfähigkeit der Verarbeitungseinheit nicht nutzen zu können, wenn sich die Daten nicht in L1 befinden (was fast immer bei jedem Streaming-Anwendungsfall der Fall ist, der größer als L1 ist). – edorado
Aus diesem Grund teilen leistungskritische Algorithmen ihre Arbeitssätze oft in Teilmengen auf, die in die kleineren Caches passen (siehe z. B. Cache-Tiling-Techniken). Laut Artikel L2 wurde auch die Bandbreite im Vergleich zu älteren CPUs erhöht, ich denke, es ist nur schwer, die L1-Verbesserungen nachzuholen. – Leeor