Alcuni rimedi informatici per i revisori PCI nel mio pubblico.
Ti do una serie di numeri interi casuali. Come puoi sapere se c'è il numero tre?
Bene, c'è il modo ovvio: controlla i numeri in sequenza fino a trovare il "3" o esaurire l'array. Ricerca lineare. Dati 10 numeri, devi presumere che potrebbe richiedere 10 passaggi; N numeri, N passaggi.
Immagine 1.png
La ricerca lineare è negativa. È difficile fare peggio che lineare. Miglioriamoci. Ordina l'array.
Immagine 2.png
Un array ordinato suggerisce una strategia diversa: saltare il centro dell'array e vedere se il valore che stai cercando è minore di (a sinistra) o maggiore di (a destra). Ripeti, tagliando l'array a metà ogni volta, fino a trovare il valore.
Ricerca binaria. Dati 10 numeri, occorreranno fino a 3 passaggi - log2 di 10 - per trovarne uno in un array ordinato. O (log n) la ricerca è fantastica. Se hai 65.000 elementi, bastano 16 passaggi per trovarne uno. Raddoppia gli elementi ed è 17 passaggi.
Ma gli array ordinati fanno schifo; per prima cosa, l'ordinamento è più costoso della ricerca lineare. Quindi non usiamo molto la ricerca binaria; invece, usiamo alberi binari.
Immagine 3.png
Per cercare un albero binario, inizi dall'alto e ti chiedi "è la mia chiave minore di (a sinistra) o maggiore di (a destra) del nodo corrente", e ripeti fino a quando ok, ok, ok, conosci già queste cose. Ma quell'albero è carino, vero?
La ricerca con un albero binario (bilanciato) è O (registro n), come la ricerca binaria, che varia con il numero di elementi nell'albero. Gli alberi binari sono fantastici: ottieni una ricerca rapida e attraversi ordinati, qualcosa che non esci da una tabella di hash. Gli alberi binari sono un'implementazione della tabella predefinita migliore rispetto alle tabelle hash. 2.
Ma gli alberi binari non sono l'unico meccanismo di ricerca strutturato ad albero. I tentativi binari di radix, chiamati anche alberi PATRICIA, funzionano come alberi binari con una differenza fondamentale. Invece di confrontare maggiore / minore di rispetto a ciascun nodo, si controlla per vedere se un bit è impostato, ramificando a destra se è impostato e a sinistra se non lo è.
Immagine 4.png
Sto tralasciando molto su come funziona il binario binario. Questo è un peccato, perché i tentativi di Radix sono notoriamente sottostimati: Sedgewick li ha infami e li ha fregati in "Algoritmi", e la pagina di Wikipedia per loro fa schifo. Le persone discutono ancora su come chiamarli! Al posto di una spiegazione di backlink e bordi etichettati in posizione di bit, ecco una piccola implementazione di Ruby.
Ecco perché i tentativi di radix sono fantastici:
Search performance varies with the key size, not the number of elements in the tree. With 16 bit keys, you’re guaranteed 16 steps
indipendentemente dal numero di elementi nell'albero, senza bilanciamento.
More importantly, radix tries give you lexicographic matching, which is a puffed-up way of saying “search with trailing wildcard”, or
"Ricerca stile riga di comando". In un albero radix, puoi cercare rapidamente "ro *" e ottenere "rome" e "romulous" e "roswell".
3.
Ti ho perso.
Mettiamolo nel contesto. I tentativi sono una struttura di dati cruciale per il routing di Internet. Il problema di routing è il seguente:
You have a routing table with entries for “10.0.1.20/32 -> a” and “10.0.0.0/16 -> b”.
You need packets for 10.0.1.20 to go to “a”
You need packets for 10.0.1.21 to to to “b”
Questo è un problema difficile da risolvere con un albero binario di base, ma con un trie radix, stai solo chiedendo "1010.0000.0000.0000.0000.0001.0100" (per 10.0.1.20) e "1010." (per 10.0.0.0 ). La ricerca lessicografica ti offre la "migliore corrispondenza" per il routing. Puoi provarlo nel codice Ruby sopra; aggiungi * "10.0.0.0" .to_ip al trie e cerca "10.0.0.1" .to_ip.
La corrispondenza tra routing e tentativi di radix è così forte che la libreria di radix trie per scopi generici più popolare (quella di CPAN) viene effettivamente rubata da GateD. A proposito, è un casino e non usarlo.
Se capisci come funziona un trie, capisci anche come funzionano le espressioni regolari. I tentativi sono un caso speciale di automi deterministici finiti (DFA), in cui i rami si basano esclusivamente su confronti di bit e si diramano sempre in avanti. Un buon motore regex sta semplicemente gestendo i DFA con più "caratteristiche". Se le mie foto hanno un senso per te, anche le foto di questo eccellente articolo sull'algoritmo di riduzione NFA-DFA di Thompson lo renderanno più intelligente. 4.
Sei un operatore di rete in un ISP backbone. Il tuo mondo è in gran parte costituito da "prefissi": coppie di reti IP / maschere di rete. Le maschere di rete in questi prefissi sono estremamente importanti per te. Ad esempio, 121/8 appartiene alla Corea; 121.128 / 10 appartiene a Korea Telecom, 121.128.10 / 24 appartiene a un cliente KT e 121.128.10.53 è un computer all'interno di quel cliente. Se stai rintracciando una botnet o un'operazione di spamming o propagazione di worm, quel numero di maschera di rete è abbastanza importante per te.
Sfortunatamente, per quanto siano importanti, da nessuna parte su un pacchetto IP c'è una "maschera di rete": le maschere di rete sono interamente un dettaglio di configurazione. Quindi, quando guardi il traffico, essenzialmente hai questi dati con cui lavorare:
ips.png
Sorprendentemente, dati abbastanza pacchetti da guardare, si tratta di informazioni sufficienti per indovinare le maschere di rete. Mentre lavorava in Sony, Kenjiro Cho ha trovato un modo davvero elegante per farlo, basato sui tentativi. Ecco come:
Prendi un trie radix binario di base, proprio come quelli usati dai router software. Ma limita il numero di nodi nella struttura, diciamo a 10.000. Su un collegamento backbone, registrando gli indirizzi dalle intestazioni IP, esaurirai 10.000 nodi in pochi istanti.
Memorizza l'elenco dei nodi in un elenco, ordinato in ordine LRU. In altre parole, quando abbini un indirizzo IP a un nodo, "tocca" il nodo, incollandolo in cima all'elenco. A poco a poco, gli indirizzi visti di frequente salgono verso l'alto e i nodi visti di rado affondano verso il basso.
Immagine 6.png
Ora il trucco. Quando esaurisci i nodi e ne hai bisogno di uno nuovo, richiedi il recupero in fondo all'elenco. Ma quando lo fai, arrotola i dati dal nodo verso l'alto nel suo genitore, in questo modo:
Immagine 5.png
10.0.1.2 e 10.0.1.3 sono fratelli / 32 secondi, le due metà di 10.0.1.2/31. Per recuperarli, uniscili in 10.0.1.2/31. Se devi richiedere il 10.0.1.2/31, puoi unirlo con 10.0.1.0/31 per formare il 10.0.1.0/30.
Ripeti, diciamo, un minuto, e le fonti straordinarie difenderanno la loro posizione nell'albero rimanendo in cima all'elenco LRU, mentre il rumore ambientale / 32 bolle fino a / 0. Per l'elenco non elaborato di IP sopra, con un albero di 100 nodi, ottieni questo.
Cho chiama questo euristico Aguri. 5.
Aguri è concesso in licenza BSD. Puoi scaricarlo e un programma driver che guarda i pacchetti tramite pcap, dalla vecchia home page di Cho. 6.
Sto andando da qualche parte con questo, ma sono 1300 parole in questo post ora, e se sei una persona algoritmi sei ormai stanco di me, e se non lo sei, sei stanco di me da adesso. Quindi, lascia che Aguri affondi, e ti darò qualcosa di bello e inutile da fare alla fine di questa settimana.
Ci sono numerosi link sparsi lì dentro. Sfortunatamente, Archive.org non conserva le immagini, solo il testo, quindi molti di loro sono andati persi. Ecco quelli che ha archiviato: