Risposte:
Le liste di salto sono più suscettibili di accesso / modifica simultanei. Herb Sutter ha scritto un articolo sulla struttura dei dati in ambienti concorrenti. Ha più informazioni approfondite.
L'implementazione più frequentemente utilizzata di un albero di ricerca binario è un albero rosso-nero . I problemi concomitanti si presentano quando l'albero viene modificato spesso deve essere riequilibrato. L'operazione di ribilanciamento può influire su ampie porzioni dell'albero, il che richiederebbe un blocco mutex su molti nodi dell'albero. L'inserimento di un nodo in un elenco di salto è molto più localizzato, solo i nodi collegati direttamente al nodo interessato devono essere bloccati.
Aggiornamento dai commenti di Jon Harrops
Ho letto l'ultimo articolo di Fraser e Harris Programmazione concorrente senza blocchi . Roba davvero buona se sei interessato a strutture dati senza lock. Il documento si concentra sulla memoria transazionale e su un'operazione teorica MCAS multi-confronto-e-scambio di parole. Entrambi sono simulati nel software in quanto nessun hardware li supporta ancora. Sono abbastanza impressionato dal fatto che siano stati in grado di creare MCAS nel software.
Non ho trovato particolarmente interessante la memoria transazionale in quanto richiede un garbage collector. Anche la memoria transazionale del software è afflitta da problemi di prestazioni. Tuttavia, sarei molto entusiasta se la memoria transazionale dell'hardware diventasse mai comune. Alla fine è ancora ricerca e non sarà utile per il codice di produzione per un altro decennio o giù di lì.
Nella sezione 8.2 confrontano le prestazioni di diverse implementazioni simultanee dell'albero. Riassumo i loro risultati. Vale la pena scaricare il pdf in quanto presenta alcuni grafici molto istruttivi alle pagine 50, 53 e 54.
Aggiornamento
Ecco un documento sugli alberi senza lock : Alberi rosso-neri senza lock usando CAS .
Non ci ho studiato a fondo, ma in superficie sembra solido.
Innanzitutto, non è possibile confrontare in modo equo una struttura di dati randomizzata con una che offre garanzie nel caso peggiore.
Una skip list equivale a un albero di ricerca binaria bilanciato in modo casuale (RBST) nel modo che è spiegato in modo più dettagliato in "Exploring the Duality Between Skip Lists and Binary Search Trees" di Dean e Jones .
Al contrario, puoi anche avere skip list deterministici che garantiscono prestazioni nel caso peggiore, cfr. Munro et al.
Contrariamente a quanto sostengono alcuni sopra, è possibile avere implementazioni di alberi di ricerca binari (BST) che funzionano bene nella programmazione concorrente. Un potenziale problema con i BST focalizzati sulla concorrenza è che non si possono facilmente ottenere le stesse garanzie sul bilanciamento come si farebbe da un albero rosso-nero (RB). (Ma gli elenchi skip "standard", ovvero randomizzati, non offrono nemmeno queste garanzie.) C'è un compromesso tra il mantenimento del bilanciamento in ogni momento e un buon accesso simultaneo (e facile da programmare), quindi di solito vengono utilizzati alberi RB rilassati quando si desidera una buona concorrenza. Il rilassamento consiste nel non riequilibrare subito l'albero. Per un sondaggio un po 'datato (1998) vedi "The Performance of Concurrent Red-Black Tree Algorithms" di Hanke [ps.gz] .
Uno dei miglioramenti più recenti su questi è il cosiddetto albero cromatico (in pratica hai un peso tale che il nero sarebbe 1 e il rosso sarebbe zero, ma permetti anche valori in mezzo). E come va un albero cromatico contro la lista salta? Vediamo cosa Brown et al. "Una tecnica generale per alberi non bloccanti" (2014) deve dire:
con 128 thread, il nostro algoritmo supera la skiplist non bloccante di Java dal 13% al 156%, l'albero AVL basato su blocco di Bronson et al. dal 63% al 224% e un RBT che utilizza la memoria transazionale del software (STM) da 13 a 134 volte
EDIT da aggiungere: la lista skip basata su lock di Pugh, che è stata comparata in Fraser and Harris (2007) "Concurrent Programming Without Lock" come prossima alla loro versione senza lock (un punto ampiamente insistito nella risposta in alto qui), è anche ottimizzato per una buona operazione simultanea, cfr. "Concurrent Maintenance of Skip Lists" di Pugh , anche se in modo piuttosto delicato. Tuttavia, un nuovo documento / 2009 "Un semplice algoritmo ottimistico salta l'elenco"di Herlihy et al., che propone un'implementazione presumibilmente più semplice (rispetto a quella di Pugh) di skip list simultanee, ha criticato Pugh per non aver fornito una prova di correttezza abbastanza convincente per loro. Lasciando da parte questo scrupolo (forse troppo pedante), Herlihy et al. mostra che la loro semplice implementazione basata su lock di un skip list in realtà non riesce a ridimensionarsi così come la sua implementazione senza lock di JDK, ma solo per contese elevate (50% di inserti, 50% di eliminazioni e 0% di ricerche) ... quale Fraser e Harris non ha provato affatto; Fraser e Harris hanno testato solo il 75% di ricerche, il 12,5% di inserti e il 12,5% di eliminazioni (nell'elenco dei salti con ~ 500K elementi). L'implementazione più semplice di Herlihy et al. si avvicina anche alla soluzione senza blocco del JDK in caso di contesa bassa che hanno testato (70% di ricerche, 20% di inserti, 10% di eliminazioni); hanno effettivamente battuto la soluzione senza blocco per questo scenario quando hanno reso la loro lista dei salti abbastanza grande, cioè passando da 200K a 2M elementi, in modo che la probabilità di contesa su qualsiasi blocco diventasse trascurabile. Sarebbe stato bello se Herlihy et al. avevano superato i loro problemi con le prove di Pugh e avevano testato anche la sua implementazione, ma purtroppo non l'hanno fatto.
EDIT2: Ho trovato un motherlode (pubblicato nel 2015) di tutti i benchmark: Gramoli "Più di quanto tu abbia mai voluto sapere sulla sincronizzazione. Synchrobench, Misurare l'impatto della sincronizzazione sugli algoritmi concorrenti" : Ecco un'immagine estratta pertinente a questa domanda.
"Algo.4" è un precursore (versione precedente, 2011) di Brown et al. Di cui sopra. (Non so quanto sia migliore o peggiore la versione 2014). "Algo.26" è quello di cui parla Herlihy sopra; come puoi vedere viene cestinato sugli aggiornamenti, e molto peggio sulle CPU Intel utilizzate qui che sulle CPU Sun dal documento originale. "Algo.28" è ConcurrentSkipListMap dal JDK; non funziona come si potrebbe sperare rispetto ad altre implementazioni di skip list basate su CAS. I vincitori in contesa alta sono "Algo.2", un algoritmo basato su lock (!!) descritto da Crain et al. in "Un albero di ricerca binario a prova di contesa" e "Algo.30" è la "lista di scorrimento rotante" da "Strutture di dati logaritmici per multicore" . ". Si informa che Gramoli è coautore di tutti e tre questi articoli dell'algoritmo vincitore. "Algo.27" è l'implementazione C ++ dell'elenco skip di Fraser.
La conclusione di Gramoli è che è molto più facile rovinare un'implementazione ad albero simultanea basata su CAS piuttosto che rovinare un elenco di salti simile. E sulla base delle cifre, è difficile non essere d'accordo. La sua spiegazione per questo fatto è:
La difficoltà di progettare un albero privo di blocchi deriva dalla difficoltà di modificare atomicamente più riferimenti. Le liste di salto sono costituite da torri collegate tra loro tramite puntatori successivi e in cui ciascun nodo punta al nodo immediatamente sotto di esso. Sono spesso considerati simili agli alberi perché ogni nodo ha un successore nella torre del successore e sotto di esso, tuttavia, una distinzione principale è che il puntatore verso il basso è generalmente immutabile, semplificando quindi la modifica atomica di un nodo. Questa distinzione è probabilmente il motivo per cui gli elenchi saltati superano gli alberi in contese pesanti, come osservato nella Figura [sopra].
Superare questa difficoltà è stata una preoccupazione fondamentale nel recente lavoro di Brown et al. Hanno un documento completamente separato (2013) "Primitive pragmatiche per strutture di dati non bloccanti" sulla costruzione di "primitive" composte multi-record LL / SC, che chiamano LLX / SCX, implementate a loro volta utilizzando (a livello di macchina) CAS. Brown et al. hanno utilizzato questo building block LLX / SCX nella loro implementazione simultanea dell'albero 2014 (ma non nel 2011).
Penso che valga la pena di riassumere qui anche le idee fondamentali dell'elenco di salto "no hot spot" / contention-friendly (CF). Aggiunge un'idea essenziale dagli alberi RB rilassati (e simili strutture di dati freddi di concrrenza): le torri non sono più costruite immediatamente dopo l'inserimento, ma ritardate fino a quando non c'è meno contesa. Al contrario, l'eliminazione di un'alta torre può creare molte contese; questo è stato osservato fin dal documento simultaneo di skip-list del 1990 di Pugh, motivo per cui Pugh ha introdotto l'inversione del puntatore sulla cancellazione (un bocconcino che la pagina di Wikipedia sugli elenchi di salti non menziona ancora oggi, purtroppo). L'elenco skip CF fa un ulteriore passo avanti e ritarda l'eliminazione dei livelli superiori di un'alta torre. Entrambi i tipi di operazioni ritardate negli elenchi skip CF vengono eseguiti da un thread separato simile a garbage collector, basato su CAS, che i suoi autori chiamano "thread di adattamento".
Il codice Synchrobench (inclusi tutti gli algoritmi testati) è disponibile su: https://github.com/gramoli/synchrobench . L'ultima Brown et al. l'implementazione (non inclusa in precedenza) è disponibile all'indirizzo http://www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.java Qualcuno ha a disposizione una macchina di 32+ core? J / K Il mio punto è che puoi gestirli da soli.
Inoltre, oltre alle risposte fornite (facilità di implementazione combinata con prestazioni comparabili a un albero bilanciato). Trovo che implementare l'attraversamento in ordine (avanti e indietro) sia molto più semplice perché un elenco di salto ha effettivamente un elenco collegato all'interno della sua implementazione.
def iterate(node): for child in iterate(left(node)): yield child; yield node; for child in iterate(right(node)): yield child;
? =). controllo non locale aw stupendo .. @Jon: scrivere in CPS è un dolore, ma forse vuoi dire con continuazioni? i generatori sono fondamentalmente un caso speciale di continuazioni per Python.
In pratica, ho scoperto che le prestazioni di B-tree nei miei progetti sono andate meglio delle skip list. Le liste di salti sembrano più facili da capire ma l'implementazione di un albero a B non è così difficile.
L'unico vantaggio che conosco è che alcune persone intelligenti hanno capito come implementare un elenco di salto simultaneo senza blocco che utilizza solo operazioni atomiche. Ad esempio, Java 6 contiene la classe ConcurrentSkipListMap e puoi leggere il codice sorgente se sei pazzo.
Ma non è neanche troppo difficile scrivere una variante B-tree simultanea - l'ho visto fare da qualcun altro - se dividi e unisci preventivamente nodi "per ogni evenienza" mentre cammini lungo l'albero, non dovrai preoccuparsi di deadlock e sempre e solo bisogno di tenere un lucchetto su due livelli dell'albero alla volta. L'overhead di sincronizzazione sarà leggermente più alto ma l'albero B è probabilmente più veloce.
Dalla Wikipedia articolo lei ha citato:
Θ (n) operazioni, che ci costringono a visitare tutti i nodi in ordine crescente (come stampare l'intero elenco) offrono l'opportunità di eseguire una derandomizzazione dietro le quinte della struttura di livello dell'elenco skip in modo ottimale, portando la lista salta al tempo di ricerca O (log n). [...] Un skip list, sul quale non abbiamo eseguito di recente [nessuna di queste] Θ (n) operazioni, non fornisce le stesse garanzie assolute di prestazioni nel caso peggiore delle strutture di dati ad albero bilanciato più tradizionali , perché è sempre possibile (sebbene con probabilità molto bassa) che i lanci di monete usati per costruire la lista dei salti producano una struttura mal bilanciata
EDIT: quindi è un compromesso: gli Skip List usano meno memoria a rischio di degenerare in un albero sbilanciato.
Le liste di salto sono implementate usando le liste.
Esistono soluzioni senza blocco per elenchi collegati singolarmente e doppiamente, ma non esistono soluzioni senza blocco che utilizzano direttamente solo CAS per qualsiasi struttura di dati O (logn).
È tuttavia possibile utilizzare elenchi basati su CAS per creare elenchi di salto.
(Si noti che MCAS, che è stato creato utilizzando CAS, consente strutture di dati arbitrarie e un albero rosso-nero di prova del concetto era stato creato utilizzando MCAS).
Quindi, per quanto siano strani, risultano molto utili :-)
Gli Skip List hanno il vantaggio di rimuovere i blocchi. Tuttavia, il tempo di autonomia dipende da come viene deciso il livello di un nuovo nodo. Di solito questo viene fatto usando Random (). Su un dizionario di 56000 parole, saltare la lista ha impiegato più tempo di un albero di visualizzazione e l'albero ha impiegato più tempo di una tabella di hash. I primi due non potevano corrispondere al runtime della tabella hash. Inoltre, l'array della tabella hash può anche essere privato del blocco.
Gli Skip List e gli elenchi ordinati simili vengono utilizzati quando è necessaria la località di riferimento. Ad esempio: ricerca di voli prima e prima di una data in un'applicazione.
Un albero di splay di ricerca binaria immemorabile è fantastico e più frequentemente utilizzato.
Skip List Vs Splay Tree Vs Hash Table Runtime sul dizionario find op