Prestazioni del codice orientato ADT a assegnazione singola su CPU moderne

32

Lavorare in dati immutabili con assegnazioni singole ha l'ovvio effetto di richiedere più memoria, si potrebbe presumere, perché si creano costantemente valori nuovi (sebbene i compilatori sotto le copertine facciano trucchi puntatori per rendere questo meno di un problema).

Ma alcune volte ho sentito che le perdite in termini di prestazioni sono controbilanciate dai guadagni nel modo in cui la CPU (il suo controller di memoria in particolare) può trarre vantaggio dal fatto che la memoria non è mutata (tanto ).

Speravo che qualcuno potesse far luce su come questo è vero (o se non lo è?)

In un commento su un altro post è stato detto che Tipi di dati astratti (ADT) hanno a che fare con ciò che mi ha ulteriormente incuriosito, in che modo gli ADT influiscono in modo specifico sul modo in cui la CPU gestisce la memoria? Questo è comunque un accantonamento, principalmente mi interessa solo il modo in cui la purezza del linguaggio influenza necessariamente le prestazioni della CPU e le sue cache ecc.

    
posta Jimmy Hoffa 04.04.2013 - 18:08
fonte

1 risposta

28

CPU (its memory controller specifically) can take advantage of the fact that the memory is not mutated

Il vantaggio è che questo fatto salva il compilatore dall'utilizzo delle istruzioni membar quando si accede ai dati.

A memory barrier, also known as a membar, memory fence or fence instruction, is a type of barrier instruction which causes a central processing unit (CPU) or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction. This typically means that certain operations are guaranteed to be performed before the barrier, and others after.

Memory barriers are necessary because most modern CPUs employ performance optimizations that can result in out-of-order execution. This reordering of memory operations (loads and stores) normally goes unnoticed within a single thread of execution, but can cause unpredictable behaviour in concurrent programs and device drivers unless carefully controlled...

Vedete, quando si accede ai dati da thread diversi, nella CPU multi-core funziona come segue: thread diversi vengono eseguiti su diversi core, ognuno con la propria cache (locale al loro core) - una copia di una cache globale .

Se i dati sono mutabili e il programmatore ha bisogno che sia coerente tra diversi thread, è necessario prendere delle misure per garantire la coerenza. Per programmatore, ciò significa utilizzare i costrutti di sincronizzazione quando accedono (ad esempio leggono) i dati in un particolare thread.

Per il compilatore, il costrutto di sincronizzazione nel codice significa che è necessario inserire un'istruzione membar per assicurarsi che le modifiche apportate alla copia dei dati in uno dei core siano correttamente propagate ("pubblicato "), per garantire che le cache su altri core abbiano la stessa copia (aggiornata).

Semplificando in qualche modo vedi nota sotto , ecco cosa succede al processore multi-core per membar:

  1. Tutti i core interrompono l'elaborazione - per evitare di scrivere accidentalmente nella cache.
  2. Tutti gli aggiornamenti apportati alle cache locali vengono riscritti su quello globale per garantire che la cache globale contenga i dati più recenti. Ci vuole un po 'di tempo.
  3. I dati aggiornati vengono riscritti dalla cache globale a quelli locali, per garantire che le cache locali contengano i dati più recenti. Ci vuole un po 'di tempo.
  4. Tutti i core riprendono l'esecuzione.

Vedete, tutti i core non stanno facendo nulla mentre i dati vengono copiati avanti e indietro tra le cache globali e locali . Questo è necessario per garantire che i dati mutabili siano sincronizzati correttamente (thread-safe). Se ci sono 4 core, tutti e 4 si fermano e aspettano mentre si sincronizzano le cache. Se ci sono 8, tutti e 8 si fermano. Se ce ne sono 16 ... beh, hai 15 core che fanno esattamente nulla mentre aspetti le cose necessarie a fare uno di questi.

Ora, vediamo cosa succede quando i dati sono immutabili? Non importa quale thread lo acceda, è garantito che sia lo stesso. Per programmatore, ciò significa non c'è bisogno di inserire costrutti di sincronizzazione quando accedono (leggi) dati in un particolare thread.

Per il compilatore, questo a sua volta significa non è necessario per inserire un'istruzione membar .

Di conseguenza, l'accesso ai dati non ha bisogno di fermare i core e attendere che i dati vengano scritti avanti e indietro tra le cache globali e locali. Questo è un vantaggio del fatto che la memoria non è mutata .

Nota la spiegazione alquanto semplificata sopra cade alcuni effetti negativi più complessi dei dati che sono mutabili, per esempio su pipelining . Per garantire l'ordine richiesto, la CPU deve invalidare le linee guida interessate dalle modifiche dei dati: questa è un'altra penalità legata alle prestazioni. Se questo viene implementato mediante l'invalida semplice (e quindi affidabile :) di tutte le tubazioni, l'effetto negativo viene ulteriormente amplificato.

    
risposta data 05.04.2013 - 17:35
fonte