Quale struttura dati dovrei usare per questa strategia di caching?

11

Sto lavorando su un'applicazione .NET 4.0, che esegue un calcolo piuttosto costoso su due doppi che restituiscono un doppio. Questo calcolo viene eseguito per ognuna delle diverse migliaia di elementi . Questi calcoli vengono eseguiti in Task su un thread del threadpool.

Alcuni test preliminari hanno dimostrato che gli stessi calcoli vengono eseguiti più e più volte, quindi mi piacerebbe memorizzare i risultati n . Quando la cache è piena, vorrei buttare fuori l'oggetto meno usato spesso usato di recente. ( Modifica: mi sono reso conto che meno spesso non ha senso, perché quando la cache è piena e sostituirò un risultato con uno appena calcolato, quello sarebbe meno utilizzato e immediatamente sostituito la prossima volta viene calcolato un nuovo risultato e aggiunto alla cache)

Per implementare questo, stavo pensando di usare un Dictionary<Input, double> (dove Input sarebbe una mini-classe che memorizza i due valori doppi di input) per memorizzare gli input ei risultati memorizzati nella cache. Tuttavia, dovrei anche tenere traccia di quando un risultato è stato utilizzato l'ultima volta. Per questo penso che avrei bisogno di una seconda raccolta che memorizza le informazioni di cui avrei bisogno per rimuovere un risultato dal dictonary quando la cache si stava riempendo. Sono preoccupato che tenere costantemente in ordine questo elenco influirebbe negativamente sulle prestazioni.

C'è un modo migliore (cioè più performante) per farlo, o forse anche una struttura di dati comune di cui non sono a conoscenza? Che tipo di cose dovrei profilare / misurare per determinare l'ottimalità della mia soluzione?

    
posta PersonalNexus 22.02.2012 - 07:02
fonte

5 risposte

12

Se vuoi usare una cache di eviction LRU (sfratto meno recente), probabilmente una buona combinazione di strutture dati da usare è:

  • Elenco collegato circolare (come coda prioritaria)
  • Dizionario

Ecco perché:

  • L'elenco collegato ha un O (1) tempo di inserimento e rimozione
  • I nodi elenco possono essere riutilizzati quando l'elenco è pieno e non è necessario eseguire allocazioni aggiuntive.

Ecco come funziona l'algoritmo di base:

Le strutture dati

LinkedList<Node<KeyValuePair<Input,Double>>> list;    Dictionary<Input,Node<KeyValuePair<Input,Double>>> dict;

  1. L'input è ricevuto
  2. Se il dizionario contiene la chiave
    • restituisce il valore memorizzato nel nodo e sposta il nodo all'inizio della lista
  3. Se il dizionario non contiene la chiave
    • calcola il valore
    • memorizza il valore nell'ultimo nodo della lista
    • se l'ultimo non ha un valore, rimuovi la chiave precedente dal dizionario
    • sposta l'ultimo nodo nella prima posizione.
    • memorizza nel dizionario la coppia di valori chiave (input, node).

Alcuni vantaggi di questo approccio sono: leggere e impostare un valore di dizionario che si avvicina a O (1), inserire e rimuovere un nodo in una lista collegata è O (1), il che significa che l'algoritmo si avvicina a O (1) per leggere e scrittura di valori nella cache, evita allocazioni di memoria e blocca le operazioni di copia della memoria, rendendola stabile dal punto di vista della memoria.

    
risposta data 22.02.2012 - 09:34
fonte
3

Questo sembra un grande sforzo per un singolo calcolo, data la potenza di elaborazione che hai a disposizione nel PC medio. Inoltre, avrai ancora la spesa della prima chiamata al tuo calcolo per ogni coppia di valori univoci, quindi 100.000 coppie di valori univoche ti costeranno comunque Tempo n * 100.000 al minimo. Considera che l'accesso ai valori nel tuo dizionario sarà probabilmente più lento man mano che il dizionario si ingrandirà. Puoi garantire che la velocità di accesso del tuo dizionario compenserà abbastanza da fornire un ritorno ragionevole rispetto alla velocità del tuo calcolo?

Indipendentemente da ciò, sembra che probabilmente dovrai considerare di trovare un modo per ottimizzare il tuo algoritmo. Per questo avrai bisogno di uno strumento di profilazione, come Redgate Ants in ordina per vedere dove sono i colli di bottiglia e per aiutarti a determinare se ci sono modi per ridurre alcuni dei sovraccarichi che potresti avere in relazione alle istanze di classi, gli attraversamenti di liste, gli accessi al database o qualunque cosa ti stia costando così tanto tempo.

    
risposta data 22.02.2012 - 08:23
fonte
0

Un pensiero è perché solo i risultati della cache n? Anche se n è 300.000, si utilizzano solo 7,2 MB di memoria (più qualsiasi extra per la struttura della tabella). Ciò presuppone naturalmente tre doppi 64 bit. Potresti semplicemente applicare la memoizzazione alla complessa routine di calcolo stessa, se non sei preoccupato di rimanere a corto di spazio di memoria.

    
risposta data 22.02.2012 - 07:18
fonte
0

L'approccio con la seconda raccolta va bene. Dovrebbe essere una coda di priorità che consente di trovare / eliminare rapidamente i valori min e anche di modificare (aumentare) le priorità all'interno della coda (quest'ultima è quella dura, non supportata dalla maggior parte delle semplici implementazioni della coda prio). La libreria C5 ha una tale raccolta, si chiama IntervalHeap .

Ovviamente, puoi provare a creare la tua collezione, qualcosa come a  %codice%. ( SortedDictionary<int, List<InputCount>> deve essere una classe che combina i tuoi dati InputCount con il tuo valore Input )

L'aggiornamento di questa raccolta quando si modifica il valore conta può essere implementato rimuovendo e reinserendo un elemento.

    
risposta data 22.02.2012 - 08:24
fonte
0

Come sottolineato nella risposta di Peter Smith, lo schema che stai cercando di implementare è chiamato memoization . In C # è piuttosto difficile implementare la memoizzazione in modo trasparente senza effetti collaterali. Il libro di Oliver Sturm nella programmazione funzionale in C # offre una soluzione (il codice è disponibile per il download, capitolo 10).

In F # sarebbe molto più facile. Ovviamente, è una decisione importante iniziare a utilizzare un altro linguaggio di programmazione, ma potrebbe valere la pena di prenderlo in considerazione. Soprattutto nei calcoli complessi, è destinato a rendere più facile programmare più cose rispetto alla memoizzazione.

    
risposta data 22.02.2012 - 11:27
fonte

Leggi altre domande sui tag