Dopo aver scritto questa risposta, dovrei probabilmente contrassegnare la domanda come "troppo ampia": possiamo parlare per secoli di varie strategie, alla fine dovrà essere eseguito un benchmark con i tuoi dati.
Ogni tag può essere rappresentato in modo efficiente da un numero intero. Ogni entità ha un set di tag. È importante scegliere l'implementazione corretta del set: sono possibili sia alberi B sia array ordinati. Con questo set, eseguiremo solo test di appartenenza. Dato che entrambe le strutture fanno questo in O (log t) (con tag t per entità), preferirei le matrici a causa della loro rappresentazione più densa.
Ora possiamo filtrare tutte le entità in un'operazione O (n · log t · p) , dove p è la lunghezza media del percorso nell'albero decisionale del predicato. Questo albero decisionale può essere ordinato in modo tale che una decisione possa essere raggiunta rapidamente. Senza dati statistici, è possibile solo fattorizzare la sottoespressione comune.
L'ordine in cui vengono cercate le entità non è molto importante. D'altra parte può essere vantaggioso di risolvere tale che le entità a indici 0
a i
tutti hanno un certo tag, mentre il resto non. Ciò riduce la n durante la ricerca di questo tag specifico (nella struttura decisionale, questo dovrebbe essere il primo test). Questo può essere espanso a più livelli, ma ciò complica le cose e richiede memoria O (2 k ) con klivelli. Con più livelli, i tag con il guadagno più alto dovrebbero essere decisi per primi, dove il guadagno è il numero di entità che non è necessario cercare volte la probabilità di scartarle. Il guadagno diventa massimo per le probabilità 50:50 o quando il 50% delle entità ha questo tag specifico. Ciò consentirebbe di ottimizzare anche se i modelli di accesso non sono noti.
È inoltre possibile creare set che indicizzano le entità in base a ciascun tag utilizzato: un set con tutte le entità per T1
, il successivo per T2
. Un'ovvia ottimizzazione (spazio e tempo) è quella di interrompere quando un set contiene più della metà di tutti gli elementi e di salvare quegli elementi che non hanno questo tag - in questo modo, la costruzione di indici per tutti i tag richiederà meno dello ½ · n · t
spazio (con t tag in totale). Notare che il salvataggio di set complementari può rendere più difficili altre ottimizzazioni. Ancora una volta, vorrei (ordinato) gli array per i set.
Se si rappresentano anche le entità attraverso un intervallo intero, è possibile comprimere lo spazio utilizzato per i set di indici memorizzando solo il membro iniziale e finale di un intervallo continuo. Dal punto di vista dell'implementazione, ciò verrebbe probabilmente fatto con un bit alto per indicare se una voce è un limite di intervallo o una voce regolare.
Se ora disponiamo di set di indici (e quindi di statistiche sui tag), possiamo ottimizzare i nostri predicati in modo da testare prima le proprietà improbabili (strategia fail-fast). Ciò significa che se T1
è comune ed T2
è raro, il predicato T1 & T2
deve essere valutato ripetendo tutte le T2
voci del set di indici e testando ciascun elemento per T1
.
Se utilizziamo array ordinati per implementare i set di indici, è possibile implementare molti passaggi di valutazione come operazioni di fusione. T1 & T2
significa che prendiamo gli elenchi T1
e T2
, allochiamo un array di destinazione delle dimensioni degli input più grandi ed eseguiamo il seguente algoritmo fino a quando entrambi gli input sono vuoti: If T1[0] < T2[0]
, then T1++
(scarta la testa). Se T1[0] > T2[0]
allora T2++
. Se entrambe le teste sono uguali, allora copi verso la matrice di destinazione, e incrementare tutte tre puntatori ( T1
, T2
, target). Se il predicato è T1 | T2
, allora nessun elemento viene scartato ma quello più piccolo viene copiato. Un predicato della forma T1 & ¬T2
può anche essere implementato usando una strategia fusione, ma ¬T1
o T1 | ¬T2
no.
Questo dovrebbe essere preso in considerazione quando si ordina l'albero delle decisioni del predicato: i complementi dovrebbero essere presenti sull'RHS di un &
, o alla fine, quando viene determinato il conteggio finale e gli elementi reali non devono essere esaminati.
Senza usare i set di indici, ogni thread può filtrare attraverso la sua parte delle entità e restituire il conteggio degli elementi che corrispondono al predicato, che può essere riassunto. Quando si utilizzano set di indici, a ciascun thread viene assegnato un nodo nella struttura decisionale. Prende due flussi di input che corrispondono ai set ordinati e restituiscono un flusso unito. Si noti che ogni nodo nella struttura decisionale ha un set corrispondente che rappresenta tutte le entità che soddisfano quella sottoespressione e che, a causa dell'ordinamento degli insiemi, non è necessario conoscere l'intero insieme in una volta per unirli .
Diverse strategie come la fusione di insiemi indicizzati o il filtraggio attraverso un elenco di entità possono essere combinate in una certa misura. Il filtro ha prestazioni molto prevedibili. Se una query è molto specifica in modo che l'utilizzo dei set di indici riduca drasticamente lo spazio di ricerca, le operazioni di unione potrebbero essere migliori. È importante notare che la fusione di molti set di input di grandi dimensioni può comportare prestazioni molto peggiori rispetto alla ricerca della forza bruta. Un algoritmo molto ottimizzato sceglierà una strategia adatta in base alla dimensione dell'input, alla struttura della query e agli indicatori statistici.
A parte questo, la memorizzazione nella cache dei risultati parziali può essere utile se si prevede che query simili verranno eseguite in futuro, anche se non accelerano la corsa iniziale.
T1
lo stesso riferimento oggetto perE1
,E2
ecc?