È possibile rappresentare in modo efficiente la mutazione del grafico a oggetti con stati immutabili?


12

Sto praticando l'uso di oggetti immutabili in C ++. Il mio obiettivo personale è rappresentare un oggetto generico (in heap) con una sequenza di grafici immutabili.

Costruire il grafico multi-versione in sé non è poi così difficile. Il problema è la prestazione. Il versioning della forza bruta richiede una copia completa del grafico e questo non era accettabile.

Ho provato a condividere nodi invariati. Ma in questo caso, ho avuto un nuovo problema; Riferimenti. Il riferimento ad altro oggetto deve essere aggiornato nell'intero grafico. Questo ha bisogno di visitare tutti i nodi ogni volta che ne ricavo una nuova versione del grafico. E questo muta i nodi con riferimenti, quindi dovrebbero anche essere derivati ​​(copiando). Le prestazioni non saranno migliori della copia a forza bruta.

Per quanto posso immaginare, non esiste un modo efficace per rappresentare la mutazione del grafico a oggetti con stati immutabili. Quindi sto chiedendo qualche idea su questo.

È possibile rappresentare in modo efficiente la mutazione del grafico a oggetti con stato immutabile?


1
È difficile solo perché stai mettendo i bordi sui nodi. Se conservassi i bordi esternamente, in una collezione immutabile, sarebbe facile.
dan_waterworth,

@dan_waterworth Se utilizzo l'elenco di adiacenza, devo cercare ogni bordo per ogni volta. Quindi penso che sia un compromesso tra le prestazioni di lettura e scrittura.
Eonil,

1
Non deve essere un elenco.
dan_waterworth,

@dan_waterworth Intendi qualcosa come B-tree?
Eonil,

2
alberi larghi come gli alberi B tendono ad essere utilizzati quando la latenza sul supporto di memorizzazione è elevata. In questo caso, potresti stare meglio con qualcosa di più stretto, ma sì, una sorta di albero di ricerca equilibrato sarebbe una buona idea.
dan_waterworth,

Risposte:


11

Quello che stai cercando si chiama Struttura dei dati persistente . La risorsa canonica per le strutture di dati persistenti è il libro Purely Functional Data Structures di Chris Okasaki . Strutture di dati persistenti hanno raccolto interesse negli ultimi tempi a causa della loro divulgazione a Clojure e Scala.

Tuttavia, per qualche strana ragione, i grafici persistenti sembrano essere per lo più ignorati. Abbiamo liste, dozzine di diversi tipi di alberi, array, code di priorità, mappe, ma nessun grafico.

Ho davvero trovato solo un articolo: grafici completamente persistenti: quale scegliere?


4

Se non consideri le connessioni tra gli oggetti come parte della tua risorsa con versione (e potresti - nel qual caso probabilmente il seguente non aiuta molto), potresti considerare di dividere gli oggetti in una parte che rappresenta la parte di connettività dell'oggetto e una parte che rappresenta lo stato immutabile.

Quindi è possibile che ciascuno degli oggetti secondari di connettività contenga un vettore degli stati con versione. In questo modo è possibile operare con un numero di versione del grafico per accedere allo stato immutabile appropriato.

Per evitare di dover attraversare l'intero grafico ogni volta che c'è un aggiornamento a un nodo specifico, è possibile farlo in modo che se si accede a un nodo con un numero di versione maggiore del numero di versione corrente del nodo, viene utilizzata la versione corrente . Se il nodo viene quindi aggiornato, si inseriscono tutte le versioni intermedie con la versione precedente, consentendo in tal modo di eseguire aggiornamenti pigri al grafico degli oggetti.

Se la connettività tra oggetti fa parte del tuo stato con versione, ciò che precede non funziona. Ma forse puoi estenderlo come segue:

Per ogni oggetto nel grafico, creare un "oggetto handle". L'oggetto handle contiene l'elenco di stati immutabili con versione. Invece di memorizzare i riferimenti agli oggetti in uno qualsiasi degli oggetti del grafico, è necessario memorizzare un riferimento all'oggetto handle. Quindi ogni riferimento agli oggetti sarebbe rinviato attraverso l'handle usando l'handle e il numero di versione della vista del grafico oggetto che era attualmente in elaborazione. Ciò restituirebbe lo stato immutabile corretto per l'oggetto. Gli stati immutabili utilizzano le maniglie per fare riferimento ad altri oggetti nel grafico, in modo da ottenere sempre la data coerente per la versione del grafico che si desidera elaborare.

La stessa logica sopra descritta si applica all'aggiornamento delle versioni all'interno degli handle, che consente aggiornamenti pigri e localizzati.


3

Esiste una soluzione pubblicata a questo problema con un'ottima complessità temporale ammortizzata, ma è difficile da trovare quando non sai esattamente cosa cercare. È possibile trovare un bel riassunto in queste note della lezione (consultare la sezione 2.2.3) o leggere il documento originale Rendere persistenti le strutture dati . Se il tuo grafico a oggetti ha una connettività limitata (ad esempio strutture ad albero) puoi persino raggiungere la complessità O (1) ammortizzata sia per la lettura che per l'aggiornamento del grafico immutabile, il che è impressionante.

L'idea è che ogni oggetto, oltre a memorizzare il suo stato immutabile attuale, riservi spazio per la registrazione delle modifiche. Quando si desidera creare un nuovo grafico immutabile da uno esistente applicando le modifiche, è necessario considerare due casi:

  • L'oggetto che si desidera modificare ha ancora spazio per le modifiche. È possibile memorizzare le modifiche direttamente nell'oggetto.

  • L'oggetto non ha più spazio per le modifiche. Si crea una nuova istanza in base ai valori correnti e svuota i record di modifica. Ora è necessario aggiornare ricorsivamente tutti gli oggetti che fanno riferimento al vecchio oggetto per fare riferimento al nuovo oggetto.

    Se l'oggetto di riferimento stesso ha ancora spazio per le modifiche, è possibile archiviare direttamente la modifica in riferimento, altrimenti ricorre in cascata in modo ricorsivo.

Mentre questo schema supporta la creazione efficiente di nuove generazioni di grafici a oggetti immutabili, ciò complica la lettura da esso, perché ora è necessario specificare a quale "versione" si desidera accedere durante la lettura dei dati da un oggetto immutabile. Questo perché l'oggetto immutabile potrebbe contenere informazioni per più versioni a causa dei record delle modifiche memorizzati, quindi è necessario specificare quale versione si desidera guardare.

Durante l'implementazione, provo a nascondere questa complessità nell'API. Quando faccio riferimento a qualcosa nel grafico immutabile dall'esterno, utilizzo stub generati anziché riferimenti diretti, quindi i chiamanti non devono continuare a passare manualmente la versione desiderata. Questo rende i riferimenti leggermente più costosi di un puntatore diretto, ma trovo che valga la pena.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.