- Algoritmi single-core
- L’algoritmo di spazio lineare L S_D L
- L’algoritmo linear-space efficiente per la cache S t r i p_D L
- L’algoritmo di traccia nello spazio lineare L S D L_T R A C E
- L’algoritmo strip trace S t r i p_T R A C E
- Algoritmi multi-core
- L’algoritmo P P_D L
- L’algoritmo P P_L S_D L
- L’algoritmo P P_S t r i p_D L
- L’algoritmo P P_D L_T R A C E
- Gli algoritmi P P_L S D L_T R A C E e P P_S t r i p_T R A C E
Algoritmi single-core
In questa sezione, sviluppiamo quattro algoritmi single-core in spazio lineare per la correzione delle stringhe usando la distanza DL. Tutti e quattro girano in tempo O(mn). I primi due (LS_DL e Strip_DL) calcolano solo il punteggio Hmn della traccia ottimale; differiscono nella loro efficienza della cache. Gli ultimi due (LSDL_TRACE e Strip_TRACE) calcolano una traccia ottimale.
L’algoritmo di spazio lineare L S_D L
Sia s la dimensione dell’alfabeto. Invece di usare l’array H usato in DL, l’algoritmo LS_DL usa un array unidimensionale U e un array bidimensionale T. Questi due array hanno una richiesta di spazio di O((s+1)n) = O(n) per s costante. Quando m<n, si può scambiare A e B per ridurre la memoria richiesta. Aggiungendo la memoria necessaria per A e B, la complessità spaziale è O(s min{m,n}+m+n) = O(m+n) quando s è una costante.
Come nell’algoritmo DL, i valori Hij sono calcolati per righe. L’array unidimensionale U è usato per salvare i valori H calcolati dall’algoritmo DL quando viene calcolata la riga i. Sia H l’ultima riga calcolata per il carattere c. Allora, T è la riga w-1 di H. L’algoritmo 2 fornisce lo pseudocodice per LS_DL. La sua correttezza segue dalla correttezza dell’algoritmo DL. Si noti che swap(T],U) richiede O(1) tempo in quanto vengono scambiati i puntatori a 2 array unidimensionali piuttosto che il contenuto di questi array. Il numero di cache-miss per LS_DL è lo stesso di quello di DL quando n è adeguatamente grande, poiché entrambi hanno lo stesso schema di accesso ai dati. Tuttavia, per istanze più piccole LS_DL mostrerà un comportamento della cache molto migliore. Per esempio, a causa del suo utilizzo di molta meno memoria, potremmo avere abbastanza cache LLC per memorizzare tutti i dati in LS_DL ma non in DL (O(sn) vs O(mn)).
L’algoritmo linear-space efficiente per la cache S t r i p_D L
Quando (s+1)n è più grande della dimensione della cache LLC, possiamo ridurre le missioni nella cache rispetto all’algoritmo LS_DL calcolando Hij per strisce di larghezza q, per qualche q inferiore a n (l’ultima striscia può avere una larghezza inferiore a q). Questo è mostrato in Fig. 3. Le strisce sono calcolate nell’ordine 0, 1,… usando l’algoritmo LS_DL. Tuttavia, lo spazio necessario a T e U in LS_DL è ridotto a (s+1)q poiché la larghezza della striscia è q piuttosto che n. Scegliendo q abbastanza piccolo, possiamo assicurarci che i blocchi degli array T e U usati da LS_DL non vengano sfrattati dalla cache una volta che sono stati portati dentro. Quindi, se ogni voce di T e U prende 1 parola, allora quando la dimensione della cache è lw, abbiamo q<lw/(s+1). Si noti che, oltre a T e U, la cache deve contenere i parziali di A, B e altri array necessari per passare i dati da una striscia alla successiva.
Per passare i dati da una striscia alla successiva, usiamo un ulteriore array unidimensionale di dimensioni m e un array bidimensionale s∗m V. La striscia di array registra i valori di H calcolati per la colonna più a destra nella striscia. V dà il valore di H nella colonna j più a destra della riga i di H che è (a) in una striscia a sinistra di quella che si sta calcolando e (b) c=B.
Lo pseudocodice per Strip_DL è dato nell’algoritmo 3. Per chiarezza, questo pseudocodice usa due array di strip (righe 18 e 30) e due array V (righe 24 e 32). Un set di array è usato per recuperare i dati calcolati per la striscia precedente e l’altro set per i dati che devono essere passati alla striscia successiva. Nell’implementazione attuale, usiamo un singolo array di strisce e un singolo array V che sovrascrive i valori ricevuti dalla striscia precedente con i valori da passare alla striscia successiva.
Il tempo e la complessità di Strip_DL sono, rispettivamente, O(mn) e O((s+1)m+(s+1)q+n) = O(sm+sq+n) = O(sm+n) poiché q è una costante. Quando m>n, possiamo scambiare A e B per conservare la memoria e quindi la complessità dello spazio diventa O(s min{m,n}+m+n) = O(m+n) per s costante.
Quando analizziamo la cache miss, notiamo che q è scelto in modo tale che U e T entrino nella cache. Facciamo l’ipotesi ragionevole che la regola di sostituzione LRU non causi lo sfratto di nessun blocco di U o T durante l’esecuzione dell’algoritmo Strip_DL. Come risultato, il numero totale di cache miss dovute a U e T è indipendente da m e n e quindi può essere ignorato nell’analisi. L’inizializzazione di strip e V risulta in m/w e (s+1)m/w accessi in lettura, rispettivamente. Il numero di accessi in scrittura è approssimativamente uguale al numero di accessi in lettura. Il calcolo per ogni striscia accede alla striscia dell’array in ordine crescente di indice. Questo comporta (approssimativamente) lo stesso numero di accessi alla cache di quelli effettuati durante la fase di inizializzazione. Quindi, il numero totale di missioni di cache dovute alla striscia è approssimativamente (2m/w)(n/q+1). Per V, notiamo che quando si calcola la striscia attuale, gli elementi in qualsiasi riga di V sono acceduti in ordine non decrescente di indice (cioè, da sinistra a destra) e che abbiamo bisogno di conservare, nella cache, solo il valore letto più di recente per ogni carattere dell’alfabeto (cioè, al massimo s valori devono essere conservati). Assumendo che un valore V sia espulso dalla cache solo quando si accede a un nuovo valore per lo stesso carattere, il numero totale di read miss da V durante il calcolo di una singola striscia è sm/w. Il numero di mancate scritture è approssimativamente lo stesso. Quindi, V contribuisce (2sm/w)(n/q+1). Quindi, il numero totale di cache miss per l’algoritmo Strip_DL è ≈2(s+1)mn/(wq) quando m e n sono grandi.
Ricordiamo che il numero approssimativo di cache miss per gli algoritmi DL e LS_DL è mn(1+3/w). Questo è (wq+3q)/(2s+2) volte quello per Strip_DL.
L’algoritmo di traccia nello spazio lineare L S D L_T R A C E
Anche se gli algoritmi LS_DL e Strip_DL determinano il punteggio (costo) di una traccia ottimale (e quindi di una sequenza di modifica ottimale) che trasforma A in B, questi algoritmi non conservano abbastanza informazioni per determinare effettivamente una traccia ottimale. Per determinare una traccia ottimale usando lo spazio lineare, adottiamo una strategia “divide et impera” simile a quella usata da Hirschberg per il semplice problema di editing delle stringhe (cioè, diciamo che una traccia ha un attraversamento centrale se contiene due linee (u1,v1) e (u2,v2), u1<u2 tali che v1>n/2 e v2≤n/2 (Fig. 4).