Ich teste die Speicherbandbreite auf einem Desktop und einem Server.
Sklyake desktop 4 cores/8 hardware threads
Skylake server Xeon 8168 dual socket 48 cores (24 per socket) / 96 hardware threads
Die Spitzenbandbreite des Systems beträgt
Peak bandwidth desktop = 2-channels*8*2400 = 38.4 GB/s
Peak bandwidth server = 6-channels*2-sockets*8*2666 = 255.94 GB/s
Ich benutze meine eigene Triadenfunktion von STREAM um die Bandbreite zu messen (vollständiger Code später)
void triad(double *a, double *b, double *c, double scalar, size_t n) {
#pragma omp parallel for
for(int i=0; i<n; i++) a[i] = b[i] + scalar*c[i];
}
Hier sind die Ergebnisse, die ich bekomme
Bandwidth (GB/s)
threads Desktop Server
1 28 16
2(24) 29 146
4(48) 25 177
8(96) 24 189
Für 1 Thread verstehe ich nicht, warum der Desktop so viel schneller ist als der Server. Laut dieser Antwort https://stackoverflow.com/a/18159503/2542702 reicht SSE aus, um die volle Bandbreite eines Dual-Channel-Systems zu erhalten. Das ist, was ich auf dem Desktop beobachte. Zwei Threads helfen nur geringfügig und 4 und 8 Threads ergeben ein schlechteres Ergebnis. Aber auf dem Server ist die Single-Thread-Bandbreite viel geringer. Warum ist das?
Auf dem Server bekomme ich die besten Ergebnisse mit 96 Threads. Ich hätte gedacht, dass es mit weit weniger Threads gesättigt wäre. Warum sind so viele Threads notwendig, um die Bandbreite auf dem Server zu sättigen? Es gibt eine große Fehlerspanne in meinen Ergebnissen und ich füge keine Fehlerschätzung hinzu. Ich habe das beste Ergebnis aus mehreren Läufen geholt.
Der Code
//gcc -O3 -march=native triad.c -fopenmp
//gcc -O3 -march=skylake-avx512 -mprefer-vector-width=512 triad.c -fopenmp
#include <stdio.h>
#include <omp.h>
#include <x86intrin.h>
void triad_init(double *a, double *b, double *c, double k, size_t n) {
#pragma omp parallel for
for(size_t i=0; i<n; i++) a[i] = k, b[i] = k, c[i] = k;
}
void triad(double *a, double *b, double *c, double scalar, size_t n) {
#pragma omp parallel for
for(size_t i=0; i<n; i++) a[i] = b[i] + scalar*c[i];
}
void triad_stream(double *a, double *b, double *c, double scalar, size_t n) {
#if defined ( __AVX512F__ ) || defined ( __AVX512__ )
__m512d scalarv = _mm512_set1_pd(scalar);
#pragma omp parallel for
for(size_t i=0; i<n/8; i++) {
__m512d bv = _mm512_load_pd(&b[8*i]), cv = _mm512_load_pd(&c[8*i]);
_mm512_stream_pd(&a[8*i], _mm512_add_pd(bv, _mm512_mul_pd(scalarv, cv)));
}
#else
__m256d scalarv = _mm256_set1_pd(scalar);
#pragma omp parallel for
for(size_t i=0; i<n/4; i++) {
__m256d bv = _mm256_load_pd(&b[4*i]), cv = _mm256_load_pd(&c[4*i]);
_mm256_stream_pd(&a[4*i], _mm256_add_pd(bv, _mm256_mul_pd(scalarv, cv)));
}
#endif
}
int main(void) {
size_t n = 1LL << 31LL;
double *a = _mm_malloc(sizeof *a * n, 64), *b = _mm_malloc(sizeof *b * n, 64), *c = _mm_malloc(sizeof *c * n, 64);
//double peak_bw = 2*8*2400*1E-3; // 2-channels*8-bits/byte*2400MHz
double peak_bw = 2*6*8*2666*1E-3; // 2-sockets*6-channels*8-bits/byte*2666MHz
double dtime, mem, bw;
printf("peak bandwidth %.2f GB/s\n", peak_bw);
triad_init(a, b, c, 3.14159, n);
dtime = -omp_get_wtime();
triad(a, b, c, 3.14159, n);
dtime += omp_get_wtime();
mem = 4*sizeof(double)*n*1E-9, bw = mem/dtime;
printf("triad: %3.2f GB, %3.2f s, %8.2f GB/s, bw/peak_bw %8.2f %%\n", mem, dtime, bw, 100*bw/peak_bw);
triad_init(a, b, c, 3.14159, n);
dtime = -omp_get_wtime();
triad_stream(a, b, c, 3.14159, n);
dtime += omp_get_wtime();
mem = 3*sizeof(double)*n*1E-9, bw = mem/dtime;
printf("triads: %3.2f GB, %3.2f s, %8.2f GB/s, bw/peak_bw %8.2f %%\n", mem, dtime, bw, 100*bw/peak_bw);
}
Ein großer Unterschied zwischen dem Skylake-Server und dem Skylake-Desktop-Prozessor ist die Verbindung zwischen den Kernen. Der Desktop-Prozessor hat eine Ringbus-Verbindung, während der Server-Prozessor ein Mesh-Netzwerk zwischen den Kernen hat. Broadwell-Server-CPUs hatten auch einen Ringbus, aber diese Lösung ist für höhere Kernzahlen nicht sehr skalierbar. Tatsächlich ist der Vorteil des Mesh-Netzwerks von Skylake-SP die große Skalierbarkeit, aber die Single-Thread-Speicherbandbreite ist sehr enttäuschend.
– Wim
28. Juni 2019 um 10:19 Uhr
Siehe auch diese Beschreibung des Speichersubsystems Skylake-SPund die Testergebnissewas bestätigt, dass die Single-Thread-Speicherbandbreite gering ist.
– Wim
28. Juni 2019 um 10:36 Uhr
@Zboson: Danke für das Kompliment. TBH, ich glaube, ich bin nicht genug Experte für Speichersubsysteme, um eine endgültige Antwort zu geben. Ich verstehe, dass eine Mesh-Verbindung skalierbarer ist als ein Ringbus, aber warum konnten sie keine Mesh-Verbindung mit mindestens der gleichen Single-Thread-DRAM-Speicherbandbreite wie eine Broadwell-Server-CPU entwerfen? Hätte das zu viel Silizium oder zu viel Strom (Wärme) gekostet? Ich kann nur raten. Mit AVX-512 möchten Sie eigentlich mehr Bandbreite als mit AVX2, nicht weniger.
– Wim
28. Juni 2019 um 13:07 Uhr
….(Fortsetzung) Beachten Sie, dass die begrenzte Single-Thread-Bandbreite es einfacher macht, skalierbare Rechenergebnisse zu erzielen. (Was mich an das bekannte Papier erinnert Zwölf Möglichkeiten, die Massen zu täuschen, wenn es um Leistungsergebnisse auf parallelen Computern geht und Goerg Hagers Blog, Die Massen täuschen.)
– Wim
28. Juni 2019 um 13:08 Uhr
@wim: Nun ja, weil Single-Threaded-Bandbreite auf SKX noch mehr Mist ist als auf BDW-EP. Und im Gegensatz zu BDW beinhaltet das leider L3-Bandbreite (laut Mystcial). Aber mein Punkt war, dass die Form der Kurve bei BDW-EP anders wäre: schneller Aufstieg zu einem Plateau und vielleicht ein Rückgang, wie ZBoson es auf SKL-Client sieht. Statt fast asymptotischer Annäherung an max.
– Peter Cordes
29. Juni 2019 um 17:49 Uhr