Perché Skylake è molto meglio di Broadwell-E per il throughput di memoria a thread singolo?

Abbiamo un semplice benchmark di memoria. Tutto ciò che fa è memcpy ripetutamente per un grande blocco di memoria.

Guardando i risultati (compilati per 64-bit) su alcune macchine diverse, le macchine Skylake hanno prestazioni significativamente migliori rispetto a Broadwell-E, mantenendo il sistema operativo (Win10-64), la velocità del processore e la velocità della RAM (DDR4-2133) uguali. Non stiamo parlando di alcuni punti percentuali, ma piuttosto di un fattore di circa 2 . Skylake è configurato a doppio canale e i risultati per Broadwell-E non variano per dual / triple / quad-channel.

Qualche idea sul perché questo potrebbe accadere? Il codice che segue è compilato in Release in VS2015 e riporta il tempo medio per completare ogni memcpy in:

64 bit: 2.2 ms per Skylake vs 4.5 ms per Broadwell-E

32 bit: 2.2 ms per Skylake vs 3.5 ms per Broadwell-E .

Possiamo ottenere un maggiore throughput di memoria su una build Broadwell-E quad-channel utilizzando più thread, e questo è bello, ma vedere una differenza così drastica per l’accesso alla memoria a thread singolo è frustrante. Qualche idea sul perché la differenza è così pronunciata?

Abbiamo anche utilizzato vari software di benchmarking e convalidano ciò che questo semplice esempio mostra: il throughput della memoria a thread singolo è decisamente migliore su Skylake.

#include  #include  #include  //Prevent the memcpy from being optimized out of the for loop _declspec(noinline) void MemoryCopy(void *destinationMemoryBlock, void *sourceMemoryBlock, size_t size) { memcpy(destinationMemoryBlock, sourceMemoryBlock, size); } int main() { const int SIZE_OF_BLOCKS = 25000000; const int NUMBER_ITERATIONS = 100; void* sourceMemoryBlock = malloc(SIZE_OF_BLOCKS); void* destinationMemoryBlock = malloc(SIZE_OF_BLOCKS); LARGE_INTEGER Frequency; QueryPerformanceFrequency(&Frequency); while (true) { LONGLONG total = 0; LONGLONG max = 0; LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds; for (int i = 0; i < NUMBER_ITERATIONS; ++i) { QueryPerformanceCounter(&StartingTime); MemoryCopy(destinationMemoryBlock, sourceMemoryBlock, SIZE_OF_BLOCKS); QueryPerformanceCounter(&EndingTime); ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; ElapsedMicroseconds.QuadPart *= 1000000; ElapsedMicroseconds.QuadPart /= Frequency.QuadPart; total += ElapsedMicroseconds.QuadPart; max = max(ElapsedMicroseconds.QuadPart, max); } std::cout << "Average is " << total*1.0 / NUMBER_ITERATIONS / 1000.0 << "ms" << std::endl; std::cout << "Max is " << max / 1000.0 << "ms" << std::endl; } getchar(); } 

La larghezza di banda della memoria a thread singolo sulle CPU moderne è limitata dalla max_concurrency / latency dei trasferimenti da L1D al resto del sistema, non dai colli di bottiglia del controller DRAM. Ogni core ha 10 Line-Fill Buffers (LFB) che tracciano richieste in sospeso a / da L1D. (E 16 voci “superqueue” che tracciano le linee da / a L2).

I chip multi-core di Intel hanno una latenza più elevata rispetto a L3 / memoria rispetto ai chip quad-core o dual-core desktop / laptop, quindi la larghezza di banda della memoria single-thread è in realtà molto peggiore su un grande Xeon, anche se la massima larghezza di banda aggregata con molti thread è molto meglio. Hanno molti più hop sul ring bus che collega core, controller di memoria e System Agent (PCIe e così via).

SKX (Skylake-server / AVX512, inclusi i chip “high-end desktop” i9) è davvero negativo per questo: L3 / la latenza della memoria è significativamente più alta di Broadwell-E / Broadwell-EP, quindi la larghezza di banda a thread singolo è ancora peggiore che su un Broadwell con un numero di core simile. (SKX utilizza una mesh invece di un ring bus perché la scala è migliore, vedi questo per i dettagli su entrambi, ma a quanto pare i fattori costanti sono cattivi nel nuovo design, forse le generazioni future avranno una migliore larghezza di banda L3 / latenza per piccoli e medi core Il L2 privato per core è rimbalzato fino a 1MiB, quindi forse L3 è intenzionalmente lento a risparmiare energia).


Un chip quad o dual core richiede solo un paio di thread (specialmente se i core + uncore (L3) hanno un clock elevato) per saturare la larghezza di banda della memoria, e uno Skylake con DDR4 dual channel veloce ha una larghezza di banda molto ampia.

Per ulteriori informazioni, consultare la sezione Piattaforms rilegate in latenza di questa risposta sulla larghezza di banda della memoria x86. (E leggi le altre parti per memcpy / memset con loop SIMD e rep movs/rep stos , e negozi NT rispetto ai normali negozi RFO e altro ancora).

Anche in relazione: cosa dovrebbe sapere ogni programmatore sulla memoria? (Aggiornamento del 2017 su cosa è ancora vero e cosa è cambiato in quell’ottimo articolo del 2007).

Finalmente ho ottenuto VTune (valutazione) attivo e funzionante. Fornisce un punteggio associato a DRAM di 0,602 (tra 0 e 1) su Broadwell-E e .324 su Skylake, con una parte enorme del ritardo di Broadwell-E derivante dalla latenza della memoria. Dato che le chiavette di memoria hanno la stessa velocità (ad eccezione del dual-channel configurato in Skylake e quad-channel in Broadwell-E), la mia ipotesi migliore è che qualcosa sul controller di memoria di Skylake sia incredibilmente migliore.

Rende l’acquisto nell’architettura Broadwell-E una richiesta molto più dura, e richiede che tu abbia davvero bisogno dei core aggiuntivi per considerarlo anche.

Ho anche avuto i conteggi delle perdite L3 / TLB. In Broadwell-E, il conteggio delle mancate TLB era circa il 20% più alto, e il numero delle missioni L3 era superiore del 36% circa.

Non penso che questa sia davvero una risposta per “perché”, quindi non la contrassegnerò come tale, ma è il più vicino che penso possa ottenere per ora. Grazie per tutti i commenti utili lungo la strada.