Qual è l'algoritmo di ricerca di sottostringa più veloce?


165

OK, quindi non sembro un idiota, dichiarerò il problema / i requisiti in modo più esplicito:

  • L'ago (motivo) e il pagliaio (testo da cercare) sono entrambi stringhe con terminazione null in stile C. Non vengono fornite informazioni sulla lunghezza; se necessario, deve essere calcolato.
  • La funzione dovrebbe restituire un puntatore alla prima corrispondenza o NULLse non viene trovata alcuna corrispondenza.
  • I casi di errore non sono ammessi. Ciò significa che qualsiasi algoritmo con requisiti di archiviazione non costanti (o costanti di grandi dimensioni) dovrà disporre di un caso di fallback per errore di allocazione (e le prestazioni nella cura del fallback contribuiscono quindi alle prestazioni del caso peggiore).
  • L'implementazione deve essere in C, anche se una buona descrizione dell'algoritmo (o collegamento a tale) senza codice va bene.

... oltre a ciò che intendo per "più veloce":

  • Deterministico O(n)dove n= lunghezza del pagliaio. (Ma potrebbe essere possibile usare idee di algoritmi che sono normalmente O(nm)(ad esempio rolling hash) se combinati con un algoritmo più robusto per dare O(n)risultati deterministici ).
  • Non esegue mai (misurabile; un paio di orologi per if (!needle[1])ecc. Vanno bene) peggio dell'algoritmo ingenuo di forza bruta, specialmente su aghi molto corti che sono probabilmente il caso più comune. (Le spese generali per la preelaborazione pesante incondizionata sono cattive, poiché sta cercando di migliorare il coefficiente lineare per gli aghi patologici a spese dei probabili aghi.)
  • Dato un ago e un pagliaio arbitrari, prestazioni comparabili o migliori (non meno del 50% di tempo di ricerca più lungo) rispetto a qualsiasi altro algoritmo ampiamente implementato.
  • A parte queste condizioni, lascio la definizione di "più veloce" a tempo indeterminato. Una buona risposta dovrebbe spiegare perché consideri l'approccio che stai suggerendo "più veloce".

La mia attuale implementazione è approssimativamente tra il 10% più lenta e 8 volte più veloce (a seconda dell'input) rispetto all'implementazione di glibc di Two-Way.

Aggiornamento: il mio attuale algoritmo ottimale è il seguente:

  • Per aghi di lunghezza 1, utilizzare strchr.
  • Per aghi di lunghezza 2-4, utilizzare le parole automatiche per confrontare 2-4 byte contemporaneamente come segue: Precaricare l'ago in un numero intero a 16 o 32 bit con spostamenti di bit e far uscire il vecchio byte / nuovi byte dal pagliaio ad ogni iterazione . Ogni byte del pagliaio viene letto esattamente una volta e comporta un controllo rispetto a 0 (fine della stringa) e un confronto a 16 o 32 bit.
  • Per aghi di lunghezza> 4, utilizzare l'algoritmo bidirezionale con una tabella di spostamento errata (come Boyer-Moore) che viene applicata solo all'ultimo byte della finestra. Per evitare il sovraccarico di inizializzazione di una tabella da 1kb, che sarebbe una perdita netta per molti aghi di lunghezza moderata, tengo un array di bit (32 byte) che indica quali voci nella tabella di spostamento sono inizializzate. I bit non impostati corrispondono ai valori di byte che non compaiono mai nell'ago, per i quali è possibile uno spostamento della lunghezza dell'ago completo.

Le grandi domande che mi restano in mente sono:

  • C'è un modo per utilizzare meglio la tabella dei turni? Boyer-Moore lo sfrutta al meglio scansionando all'indietro (da destra a sinistra) ma Two-Way richiede una scansione da sinistra a destra.
  • Gli unici due algoritmi candidati validi che ho trovato per il caso generale (nessuna condizione di memoria esaurita o quadratica) sono la corrispondenza a due vie e stringa sugli alfabeti ordinati . Ma ci sono casi facilmente rilevabili in cui algoritmi diversi sarebbero ottimali? Certamente molti degli algoritmi spaziali O(m)(dov'è la mlunghezza dell'ago) potrebbero essere usati per m<100circa. Sarebbe anche possibile utilizzare algoritmi che sono quadratici nel caso peggiore se esiste un test facile per aghi che dimostrano chiaramente solo un tempo lineare.

Punti bonus per:

  • Puoi migliorare le prestazioni supponendo che l'ago e il pagliaio siano entrambi UTF-8 ben formati? (Con caratteri di diverse lunghezze di byte, la ben formata impone alcuni requisiti di allineamento delle stringhe tra l'ago e il pagliaio e consente spostamenti automatici di 2-4 byte quando si incontra un byte di testa non corrispondente. Ma questi vincoli ti comprano molto / qualunque cosa oltre calcoli del suffisso massimo, buoni spostamenti del suffisso, ecc. ti danno già con vari algoritmi?)

Nota: sono ben consapevole della maggior parte degli algoritmi là fuori, ma non di quanto bene si comportino nella pratica. Ecco un buon riferimento in modo che le persone non continuino a darmi riferimenti sugli algoritmi come commenti / risposte: http://www-igm.univ-mlv.fr/~lecroq/string/index.html


Esistono numerosi algoritmi di ricerca di stringhe elencati in Algorithms on Strings . Potresti voler descrivere quali algoritmi hai considerato da questo elenco.
Greg Hewgill,

61
Quel link alla fine è l'oro!
Carlos,

4
Non posso credere che tu non abbia ancora accettato una risposta.
user541686

1
@Mehrdad: Stavo per dire che non ci sono risposte che rispondano davvero alla domanda come posta, ma la tua sembra. Nel momento in cui hai risposto che ero passato e avevo lasciato un ulteriore miglioramento strstrcome qualcosa per dopo, quindi non sono davvero riuscito a leggere correttamente il documento che hai collegato, ma sembra molto promettente. Grazie e scusa per non averti contattato.
R .. GitHub smette di aiutare ICE

Risposte:


37

Costruisci una libreria di test di probabili aghi e covoni di fieno. Profilare i test su diversi algoritmi di ricerca, inclusa la forza bruta. Scegli quello che funziona meglio con i tuoi dati.

Boyer-Moore utilizza una tabella di caratteri non valida con una tabella di suffissi valida.

Boyer-Moore-Horspool utilizza una tabella di caratteri non valida .

Knuth-Morris-Pratt utilizza una tabella delle partite parziale.

Rabin-Karp usa hash in esecuzione.

Tutti scambiano spese generali per confronti ridotti in misura diversa, quindi le prestazioni del mondo reale dipenderanno dalle lunghezze medie sia dell'ago che del pagliaio. Più sovraccarico iniziale, meglio è con input più lunghi. Con aghi molto corti, la forza bruta può vincere.

Modificare:

Un algoritmo diverso potrebbe essere la soluzione migliore per trovare coppie di basi, frasi inglesi o parole singole. Se ci fosse un algoritmo migliore per tutti gli input, sarebbe stato pubblicizzato.

Pensa al seguente piccolo tavolo. Ogni punto interrogativo potrebbe avere un diverso algoritmo di ricerca migliore.

                 short needle     long needle
short haystack         ?               ?
long haystack          ?               ?

Questo dovrebbe davvero essere un grafico, con una gamma di input più brevi o più lunghi su ciascun asse. Se hai tracciato ciascun algoritmo su un tale grafico, ognuno avrebbe una firma diversa. Alcuni algoritmi soffrono di molte ripetizioni nel modello, che potrebbero influenzare usi come la ricerca di geni. Alcuni altri fattori che influenzano le prestazioni complessive sono la ricerca dello stesso modello più di una volta e la ricerca di modelli diversi contemporaneamente.

Se avessi bisogno di un set di esempio, penso che avrei raschiato un sito come Google o Wikipedia, quindi rimuovere l'html da tutte le pagine dei risultati. Per un sito di ricerca, digitare una parola, quindi utilizzare una delle frasi di ricerca suggerite. Scegli alcune lingue diverse, se applicabile. Usando le pagine web, tutti i testi sarebbero da corti a medi, quindi unisci abbastanza pagine per ottenere testi più lunghi. Puoi anche trovare libri di dominio pubblico, documenti legali e altri grandi volumi di testo. O semplicemente generare contenuti casuali selezionando le parole da un dizionario. Ma il punto della profilazione è testare il tipo di contenuto che si cercherà, quindi utilizzare campioni del mondo reale, se possibile.

Ho lasciato vaghi e lunghi vaghi. Per l'ago, penso a breve come meno di 8 caratteri, medio come meno di 64 caratteri e lungo come meno di 1k. Per il pagliaio, penso a breve come sotto 2 ^ 10, medio come sotto 2 ^ 20 e fino a 2 ^ 30 caratteri.


1
Hai buoni suggerimenti per una libreria di test? La domanda precedente che ho posto su SO era collegata a questo e non ho mai avuto risposte reali. (tranne il mio ...) Dovrebbe essere esteso. Anche se la mia idea di una domanda di strstr è alla ricerca del testo inglese, qualcun altro potrebbe essere alla ricerca di geni in sequenze di coppie di basi ...
R .. GitHub smettere di aiutare ICE

3
È un po 'più complicato di corto / lungo. Per l'ago, le grandi domande relative alle prestazioni della maggior parte degli algoritmi sono: Lunghezza? C'è qualche periodicità? L'ago contiene tutti i caratteri univoci (nessuna ripetizione)? O lo stesso personaggio? Ci sono molti personaggi nel pagliaio che non compaiono mai nell'ago? Esiste la possibilità di dover gestire gli aghi forniti da un utente malintenzionato che desidera sfruttare le prestazioni del caso peggiore per paralizzare il sistema? Ecc.
R .. GitHub FERMA AIUTANDO ICE

31

Pubblicato nel 2011, credo che potrebbe benissimo essere l' algoritmo "Simple Matching Constant-Space String Matching" di Dany Breslauer, Roberto Grossi e Filippo Mignosi.

Aggiornare:

Nel 2014 gli autori hanno pubblicato questo miglioramento: verso una corrispondenza ottimale delle stringhe impaccate .


1
Wow grazie. Sto leggendo il giornale. Se risulta essere migliore di quello che ho, accetterò sicuramente la tua risposta.
R .. GitHub FERMA AIUTANDO ICE

1
@R ..: Sicuro! :) A proposito, se riesci a implementare l'algoritmo, considera di pubblicarlo su StackOverflow in modo che tutti possano trarne vantaggio! Non ho trovato alcuna implementazione di esso da nessuna parte e non sono bravo a implementare algoritmi che trovo nei documenti di ricerca haha.
user541686

2
È una variante dell'algoritmo "bidirezionale" che sto già utilizzando, quindi adattare il mio codice per usarlo potrebbe in realtà essere facile. Dovrò leggere il documento in modo più dettagliato per essere sicuro, però, e devo valutare se le modifiche apportate sono compatibili con il mio uso di una "tabella dei caratteri errati" che accelera notevolmente il caso comune.
R .. GitHub FERMA AIUTANDO ICE

11
E non hai ancora accettato la risposta di @ Mehrdad! :-)
lifebalance

3
@DavidWallace: What? Ha i titoli cartacei e gli autori. Anche se il collegamento si interrompe, puoi trovare i documenti. Cosa ti aspetti che faccia, scrivere pseudocodice per l'algoritmo? Cosa ti fa pensare che capisca l'algoritmo?
user541686

23

Il link http://www-igm.univ-mlv.fr/~lecroq/string/index.html a cui fai riferimento è un'eccellente fonte e un riepilogo di alcuni degli algoritmi di corrispondenza delle stringhe più noti e ricercati.

Le soluzioni alla maggior parte dei problemi di ricerca comportano compromessi per quanto riguarda i requisiti generali, di tempo e di spazio di pre-elaborazione. Nessun singolo algoritmo sarà ottimale o pratico in tutti i casi.

Se il tuo obiettivo è progettare un algoritmo specifico per la ricerca di stringhe, ignora il resto di ciò che devo dire, se desideri sviluppare una routine di servizio di ricerca di stringhe generalizzata, prova quanto segue:

Dedica un po 'di tempo alla revisione dei punti di forza e di debolezza specifici degli algoritmi a cui hai già fatto riferimento. Conduci la revisione con l'obiettivo di trovare una serie di algoritmi che coprano l'intervallo e l'ambito delle ricerche di stringhe che ti interessano. Quindi, crea un selettore di ricerca front-end basato su una funzione di classificazione per indirizzare l'algoritmo migliore per gli input dati. In questo modo è possibile utilizzare l'algoritmo più efficiente per svolgere il lavoro. Ciò è particolarmente efficace quando un algoritmo è molto buono per determinate ricerche ma si degrada male. Ad esempio, la forza bruta è probabilmente la migliore per gli aghi di lunghezza 1 ma si degrada rapidamente all'aumentare della lunghezza dell'ago, dopodiché l' algoritmo sustik-moorepotrebbe diventare più efficiente (su piccoli alfabeti), quindi per aghi più lunghi e alfabeti più grandi, gli algoritmi KMP o Boyer-Moore potrebbero essere migliori. Questi sono solo esempi per illustrare una possibile strategia.

L'approccio con algoritmo multiplo non è una nuova idea. Credo che sia stato utilizzato da alcuni pacchetti di ordinamento / ricerca commerciali (ad es. SYNCSORT comunemente usato sui mainframe implementa diversi algoritmi di ordinamento e utilizza l'euristica per scegliere quello "migliore" per gli input dati)

Ogni algoritmo di ricerca presenta diverse varianti che possono fare differenze significative nelle sue prestazioni, come, ad esempio, questo documento illustra.

Confronta il tuo servizio per classificare le aree in cui sono necessarie ulteriori strategie di ricerca o per ottimizzare in modo più efficace la tua funzione di selezione. Questo approccio non è rapido o semplice, ma se fatto bene può produrre risultati molto buoni.


1
Grazie per la risposta, in particolare il collegamento a Sustik-Moore che non avevo mai visto prima. L'approccio basato su algoritmi multipli è sicuramente molto diffuso. Fondamentalmente Glibc esegue strchr, Two-Way senza tabella di spostamento caratteri errata o Two-Way con tabella di spostamento caratteri errata, a seconda che l'ago_len sia 1, <32 o> 32. Il mio approccio attuale è lo stesso tranne per il fatto che uso sempre la tabella dei turni; Ho sostituito il memset da 1kb necessario per farlo con un memset da 32 byte su un bitset usato per contrassegnare quali elementi della tabella sono stati inizializzati, e ottengo il vantaggio (ma non il sovraccarico) anche per i piccoli aghi.
R .. GitHub smette di aiutare ICE il

1
Dopo averci pensato, sono davvero curioso di sapere quale sia l'applicazione prevista per Sustik-Moore. Con i piccoli alfabeti, non riuscirai mai a fare cambiamenti significativi (tutti i caratteri dell'alfabeto appaiono quasi sicuramente vicino alla fine dell'ago) e gli approcci agli automi finiti sono molto efficienti (piccola tabella di transizione dello stato). Quindi non riesco a immaginare nessuno scenario in cui Sustik-Moore potrebbe essere ottimale ...
R .. GitHub

ottima risposta - se potessi recitare questa particolare risposta lo farei.
Jason S

1
@R .. La teoria alla base dell'algoritmo sustik-moore è che dovrebbe fornirti quantità di spostamento medie maggiori quando l'ago è relativamente grande e l'alfabeto è relativamente piccolo (es. Ricerca di sequenze di DNA). Più grande in questo caso significa solo più grande di quanto l'algoritmo di Boyer-Moore di base produrrebbe dati gli stessi input. Quanto sia più efficiente questo è relativo ad un approccio agli automi finiti o ad altre variazioni di Boyer-Moore (di cui ce ne sono molte) è difficile da dire. Ecco perché ho sottolineato di dedicare un po 'di tempo alla ricerca dei punti di forza / di debolezza specifici dei tuoi algoritmi candidati.
NealB,

1
Hm, immagino di essere rimasto bloccato pensando ai cambiamenti solo nel senso di cattivi cambiamenti di carattere da Boyer-Moore. Con un miglioramento dei buoni turni di suffisso BM, tuttavia, Sustik-Moore potrebbe sovraperformare gli approcci DFA alla ricerca del DNA. Roba ordinata.
R .. GitHub smette di aiutare ICE il

21

Sono stato sorpreso di vedere il nostro rapporto tecnico citato in questa discussione; Sono uno degli autori dell'algoritmo soprannominato Sustik-Moore. (Non abbiamo usato quel termine nel nostro documento.)

Volevo qui sottolineare che per me la caratteristica più interessante dell'algoritmo è che è abbastanza semplice provare che ogni lettera viene esaminata al massimo una volta. Per le versioni precedenti di Boyer-Moore hanno dimostrato che ogni lettera è esaminata al massimo 3 e successivamente 2 volte al massimo, e quelle prove sono state più coinvolte (vedi citazioni in carta). Quindi vedo anche un valore didattico nel presentare / studiare questa variante.

Nel documento descriviamo anche ulteriori variazioni che sono orientate all'efficienza mentre allentano le garanzie teoriche. È un breve documento e secondo me il materiale dovrebbe essere comprensibile per un diplomato medio.

Il nostro obiettivo principale era portare questa versione all'attenzione di altri che possono migliorarla ulteriormente. La ricerca di stringhe ha così tante varianti e da soli non possiamo pensare a tutti dove questa idea potrebbe portare benefici. (Correzione del testo e modifica del modello, correzione del diverso testo del testo, preelaborazione possibile / non possibile, esecuzione parallela, ricerca di sottoinsiemi corrispondenti in testi di grandi dimensioni, errori, corrispondenze vicine ecc. Ecc.)


1
Sei a conoscenza di un'implementazione C o C ++ disponibile? Sto pensando di usarlo per alcune ricerche di motivi di DNA (corrispondenze esatte di motivi). In caso contrario, forse proverò a sviluppare un'implementazione da solo e invio a potenziare l'algoritmo
JDiMatteo

4
Senza un'implementazione disponibile nota, l'algoritmo Sustik-Moore / 2BLOCK sembra improbabile che venga utilizzato nella pratica e continui a essere omesso dai risultati in documenti di sintesi come "Il problema della corrispondenza esatta delle stringhe: una valutazione sperimentale completa"
JDiMatteo,

18

L'algoritmo di ricerca di sottostringa più veloce dipenderà dal contesto:

  1. la dimensione dell'alfabeto (ad es. DNA vs inglese)
  2. la lunghezza dell'ago

L'articolo del 2010 "Il problema della corrispondenza esatta delle stringhe: una valutazione sperimentale completa" fornisce tabelle con tempi di esecuzione per 51 algoritmi (con diverse dimensioni di alfabeto e lunghezze dell'ago), in modo da poter scegliere l'algoritmo migliore per il tuo contesto.

Tutti questi algoritmi hanno implementazioni in C, oltre a una suite di test, qui:

http://www.dmi.unict.it/~faro/smart/algorithms.php


4

Un'ottima domanda. Aggiungi solo alcuni pezzetti ...

  1. Qualcuno stava parlando della corrispondenza della sequenza del DNA. Ma per la sequenza del DNA, ciò che di solito facciamo è costruire una struttura di dati (ad esempio array di suffissi, albero di suffissi o indice FM) per il pagliaio e abbinare molti aghi contro di esso. Questa è una domanda diversa

  2. Sarebbe davvero bello se qualcuno volesse fare un benchmark di vari algoritmi. Esistono ottimi benchmark sulla compressione e sulla costruzione di array di suffissi, ma non ho visto un benchmark sulla corrispondenza delle stringhe. I potenziali candidati al pagliaio potrebbero provenire dal benchmark SACA .

  3. Qualche giorno fa stavo testando l'implementazione di Boyer-Moore dalla pagina che mi hai consigliato (EDIT: ho bisogno di una chiamata di funzione come memmem (), ma non è una funzione standard, quindi ho deciso di implementarla). Il mio programma di benchmarking utilizza un pagliaio casuale. Sembra che l'implementazione di Boyer-Moore in quella pagina sia molto più veloce di memmem () di glibc e strnstr () di Mac. Nel caso siate interessati, l'implementazione è qui e il codice di benchmarking è qui . Questo non è sicuramente un punto di riferimento realistico, ma è un inizio.


Se hai dei buoni aghi da testare insieme ai candidati al pagliaio del benchmark SACA, pubblicali come risposta all'altra mia domanda e, a corto di ottenere una risposta migliore, lo segnerò come accettato.
R .. GitHub smette di aiutare ICE l'

3
A proposito del tuo memmem e di Boyer-Moore, è molto probabile che Boyer-Moore (o piuttosto uno dei miglioramenti di Boyer-Moore) funzionerà al meglio su dati casuali. I dati casuali hanno una probabilità estremamente bassa di periodicità e corrispondenze parziali lunghe che portano al caso peggiore quadratico. Sto cercando un modo per combinare Boyer-Moore e Two-Way o per rilevare in modo efficiente quando Boyer-Moore è "sicuro da usare", ma finora non ho avuto alcun successo. A proposito, non vorrei usare il memmem di glibc come confronto. La mia implementazione di quello che è sostanzialmente lo stesso algoritmo di glibc è parecchie volte più veloce.
R .. GitHub smette di aiutare ICE l'

Come ho detto, non è la mia implementazione. Ringraziamo Christian Charras e Thierry Lecroq. Posso immaginare perché l'input casuale sia negativo per il benchmarking e sono sicuro che glibc scelga gli algoritmi per motivi. Suppongo anche che memmem () non sia implementato in modo efficiente. Cercherò. Grazie.
user172818,

4

So che è una vecchia domanda, ma la maggior parte delle tabelle turni errate sono a carattere singolo. Se ha senso per il tuo set di dati (ad esempio, soprattutto se sono parole scritte), e se hai lo spazio disponibile, puoi ottenere una notevole velocità utilizzando una tabella di turni errata fatta di n-grammi anziché singoli caratteri.


3

Usa stdlib strstr:

char *foundit = strstr(haystack, needle);

È stato molto veloce, mi ci sono voluti solo circa 5 secondi per scrivere.


26
E se leggi la mia domanda vedresti che mi sono divertito abbastanza a superarla. Mi piace abbastanza il tuo sarcasmo, però salterò il -1.
R .. GitHub smette di aiutare ICE il

3

Ecco l' implementazione della ricerca di Python , utilizzata da tutto il core. I commenti indicano che utilizza una tabella delta 1 boyer-moore compressa .

Ho fatto una sperimentazione piuttosto ampia con la ricerca di stringhe da solo, ma era per stringhe di ricerca multiple. Le implementazioni di assiemi di Horspool e Bitap possono spesso resistere agli algoritmi come Aho-Corasick per un basso numero di schemi.


3

Un strchralgoritmo "Cerca un singolo carattere corrispondente" (ala ) più veloce .

Note importanti:

  • Queste funzioni usano un gcccompilatore "numero / conteggio di zeri (iniziali | finali)" intrinseco- __builtin_ctz. È probabile che queste funzioni siano veloci solo su macchine che hanno un'istruzione o istruzioni che eseguono questa operazione (ad esempio, x86, ppc, arm).

  • Queste funzioni presuppongono che l'architettura di destinazione possa eseguire carichi non allineati a 32 e 64 bit. Se l'architettura di destinazione non lo supporta, sarà necessario aggiungere una logica di avvio per allineare correttamente le letture.

  • Queste funzioni sono neutre dal processore. Se la CPU di destinazione ha istruzioni vettoriali, potresti essere in grado di fare (molto) meglio. Ad esempio, La strlenfunzione seguente utilizza SSE3 e può essere banalmente modificata in XOR i byte scansionati per cercare un byte diverso da 0. Benchmark eseguiti su un laptop Core 2 a 2,66 GHz con Mac OS X 10.6 (x86_64):

    • 843.433 MB / s per strchr
    • 2656.742 MB / s per findFirstByte64
    • 13094.479 MB / s per strlen

... una versione a 32 bit:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu); (_x == 0u)   ? 0 : (__builtin_clz(_x) >> 3) + 1; })
#else
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu);                    (__builtin_ctz(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte32(unsigned char *ptr, unsigned char byte) {
  uint32_t *ptr32 = (uint32_t *)ptr, firstByte32 = 0u, byteMask32 = (byte) | (byte << 8);
  byteMask32 |= byteMask32 << 16;
  while((firstByte32 = findFirstZeroByte32((*ptr32) ^ byteMask32)) == 0) { ptr32++; }
  return(ptr + ((((unsigned char *)ptr32) - ptr) + firstByte32 - 1));
}

... e una versione a 64 bit:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full); (_x == 0ull) ? 0 : (__builtin_clzll(_x) >> 3) + 1; })
#else
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full);                    (__builtin_ctzll(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte64(unsigned char *ptr, unsigned char byte) {
  uint64_t *ptr64 = (uint64_t *)ptr, firstByte64 = 0u, byteMask64 = (byte) | (byte << 8);
  byteMask64 |= byteMask64 << 16;
  byteMask64 |= byteMask64 << 32;
  while((firstByte64 = findFirstZeroByte64((*ptr64) ^ byteMask64)) == 0) { ptr64++; }
  return(ptr + ((((unsigned char *)ptr64) - ptr) + firstByte64 - 1));
}

Modifica 2011/06/04 L'OP sottolinea nei commenti che questa soluzione ha un "bug insormontabile":

può leggere oltre il byte ricercato o il terminatore null, che potrebbe accedere a una pagina o pagina non mappata senza autorizzazione di lettura. Semplicemente non puoi usare letture di grandi dimensioni nelle funzioni di stringa a meno che non siano allineate.

Questo è tecnicamente vero, ma si applica praticamente a qualsiasi algoritmo che opera su blocchi più grandi di un singolo byte, incluso il metodo suggerito dall'OP nei commenti:

strchrUn'implementazione tipica non è ingenua, ma un po 'più efficiente di quella che hai dato. Vedi la fine di questo per l'algoritmo più utilizzato: http://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord

Inoltre, non ha nulla a che fare con l' allineamento di per sé. È vero, ciò potrebbe potenzialmente causare il comportamento discusso sulla maggior parte delle architetture comuni in uso, ma ciò ha più a che fare con i dettagli dell'implementazione della microarchitettura: se la lettura non allineata si trova a cavallo di un confine 4K (di nuovo, tipico), allora quella lettura causerà un programma errore risolto se il limite della pagina 4K successiva non è mappato.

Ma questo non è un "bug" nell'algoritmo indicato nella risposta: tale comportamento è dovuto al fatto che funzioni come strchre strlennon accettano un lengthargomento per limitare la dimensione della ricerca. La ricerca char bytes[1] = {0x55};, che ai fini della nostra discussione si trova proprio alla fine di un limite di pagina di VM VM 4K e la pagina successiva non è mappata, con strchr(bytes, 0xAA)(dove si strchrtrova un'implementazione byte per volta) si bloccherà esattamente il stessa strada. Idem perstrchr cugino affine strlen.

Senza un lengthargomento, non c'è modo di dire quando è necessario uscire dall'algoritmo ad alta velocità e tornare a un algoritmo byte per byte. Un "bug" molto più probabile sarebbe leggere "oltre la dimensione dell'allocazione", che risulta tecnicamente undefined behaviorconforme ai vari standard del linguaggio C e sarebbe contrassegnato come errore da qualcosa di similevalgrind .

In sintesi, tutto ciò che opera su blocchi di byte più grandi per andare più veloce, come fa questo codice di risposta e il codice indicato dall'OP, ma deve avere una semantica di lettura accurata in byte sarà probabilmente "buggy" se non c'è length argomenti per controlla i casi angolari dell '"ultima lettura".

Il codice in questa risposta è un kernel per essere in grado di trovare rapidamente il primo byte in una porzione di parole della CPU naturale se la CPU di destinazione ha ctzun'istruzione simile veloce . È banale aggiungere cose come assicurarsi che funzioni solo su confini naturali correttamente allineati o in qualche formalength limite, che ti permetterebbe di passare dal kernel ad alta velocità e ad un controllo byte per byte più lento.

L'OP afferma inoltre nei commenti:

Per quanto riguarda l'ottimizzazione ctz, fa solo la differenza per l'operazione di coda O (1). Potrebbe migliorare le prestazioni con stringhe minuscole (ad esempio, strchr("abc", 'a');ma certamente non con stringhe di dimensioni maggiori.

Se questa affermazione sia vera dipende molto dalla microarchitettura in questione. Utilizzando il modello canonico RISC a 4 stadi della pipeline, allora è quasi certamente vero. Ma è estremamente difficile dire se è vero per una CPU super scalare moderna fuori servizio in cui la velocità del core può ridurre completamente la velocità di streaming della memoria. In questo caso, non è solo plausibile, ma abbastanza comune, che ci sia un grande divario nel "numero di istruzioni che possono essere ritirate" rispetto al "numero di byte che possono essere trasmessi in streaming" in modo da avere "il numero di istruzioni che possono essere ritirate per ogni byte che può essere trasmesso in streaming ". Se questo è abbastanza grande, l' ctzistruzione + shift può essere eseguita "gratuitamente".


"Per aghi di lunghezza 1, utilizzare strchr." - È stato richiesto l'algoritmo di ricerca della sottostringa più veloce. Trovare una sottostringa di lunghezza 1 è solo un caso speciale, che può anche essere ottimizzato. Se si sostituisce il codice del caso speciale corrente per sottostringhe di lunghezza 1 ( strchr) con qualcosa di simile a quanto sopra, le cose (probabilmente, a seconda di come strchrviene implementata) andranno più veloci. L'algoritmo sopra è quasi 3 volte più veloce di una tipica strchrimplementazione ingenua .
johne,

2
OP ha detto che la stringa è stata correttamente annullata, quindi la tua discussione char bytes[1] = {0x55};è irrilevante. Molto pertinente è il tuo commento sul fatto che ciò sia vero per qualsiasi algoritmo di lettura di parole che non conosce in anticipo la lunghezza.
Seth Robertson,

1
Il problema non si applica alla versione che ho citato perché lo usi solo su puntatori allineati, almeno è quello che fanno le implementazioni corrette.
R .. GitHub smette di aiutare ICE il

2
@R, non ha nulla a che fare con i "puntatori allineati". Ipoteticamente, se si disponeva di un'architettura che supportava la protezione VM con granularità a livello di byte e ogni mallocallocazione era "sufficientemente riempita" su entrambi i lati e il sistema VM applicava una protezione granulare byte per tale allocazione .... indipendentemente dal fatto che il puntatore fosse allineato ( supponendo che un banale intallineamento naturale a 32 bit ) sia controverso, è ancora possibile per quella lettura allineata leggere oltre la dimensione dell'allocazione. QUALSIASI lettura oltre la dimensione dell'allocazione è undefined behavior.
johne,

5
@johne: +1 per commentare. Concettualmente hai ragione, ma la realtà è che le protezioni di byte-granularità sono così costose sia da immagazzinare che da far rispettare che non esistono e non esisteranno mai. Se si conosce che l'archiviazione sottostante è mappature di granularità di pagina ottenute dall'equivalente di mmap, l'allineamento è sufficiente.
R .. GitHub FERMA AIUTANDO ICE

3

Cerca "strstr più veloce" e se vedi qualcosa di interessante, chiedimi pure.

Dal mio punto di vista imponi troppe restrizioni a te stesso (sì, tutti noi vogliamo tutti i lineari sub-lineari al massimo ricercatore), tuttavia ci vuole un vero programmatore per intervenire, fino ad allora penso che l'approccio hash sia semplicemente una soluzione ingegnosa ( ben rinforzato da BNDM per modelli 2..16 più corti).

Solo un rapido esempio:

Facendo Cerca testo (32bytes) nella stringa (206908949bytes) come-un-linea ... Skip-Performance (grande-il-migliore): 3041%, 6801754 salta / iterazioni Railgun_Quadruplet_7Hasherezade_hits / Railgun_Quadruplet_7Hasherezade_clocks: 0/58 Railgun_Quadruplet_7Hasherezade prestazioni: 3483KB / orologio

Ricerca di Pattern (32bytes) in String (206908949bytes) come una riga ... Skip-Performance (più grande del meglio): 1554%, 13307181 salta / iterazioni Boyer_Moore_Flensburg_hits / Boyer_Moore_Flensburg_clocks: 0/83 Boyer_Moore_Flensburg : 0 / orologio

Ricerca di Pattern (32bytes) in String (206908949bytes) come una riga ... Skip-Performance (più grande del migliore): 129%, 160239051 salta / iterazioni Two-Way_hits / Two-Way_clocks: 0/816 Two -Con prestazioni: 247 KB / orologio

Sanmayce,
saluti


3

L'algoritmo a due vie che menzioni nella tua domanda (che tra l'altro è incredibile!) È stato recentemente migliorato per funzionare in modo efficiente su parole multibyte alla volta: corrispondenza delle stringhe impacchettata ottimale .

Non ho letto l'intero documento, ma sembra che facciano affidamento su un paio di nuove, speciali istruzioni della CPU (incluse in SSE 4.2) che sono O (1) per la loro richiesta di complessità temporale, anche se se non sono disponibili possono simularli in O (log log w) per le parole w-bit che non suonano troppo male.


3

Potresti implementare, diciamo, 4 algoritmi diversi. Ogni M minuti (da determinare empiricamente) esegui tutti e 4 i dati reali attuali. Accumula le statistiche su N run (anche TBD). Quindi utilizzare solo il vincitore per i prossimi M minuti.

Registra le statistiche sulle vittorie in modo da poter sostituire gli algoritmi che non vincono mai con quelli nuovi. Concentrare gli sforzi di ottimizzazione sulla routine più vincente. Prestare particolare attenzione alle statistiche dopo eventuali modifiche all'hardware, al database o all'origine dati. Se possibile, includi tali informazioni nel registro delle statistiche, quindi non dovrai capirle dalla data / data / ora del registro.



2

Potresti anche voler avere diversi benchmark con diversi tipi di stringhe, poiché ciò potrebbe avere un grande impatto sulle prestazioni. Gli algos eseguiranno differenze in base alla ricerca del linguaggio naturale (e anche qui potrebbero esserci ancora distinzioni a grana fine a causa delle diverse morfologie), stringhe di DNA o stringhe casuali ecc.

La dimensione dell'alfabeto avrà un ruolo in molti alghe, così come la dimensione dell'ago. Ad esempio, Horspool fa bene con il testo inglese ma male con il DNA a causa delle diverse dimensioni dell'alfabeto, rendendo la vita difficile per la regola dei cattivi personaggi. L'introduzione del buon suffisso lo allieta notevolmente.


0

Non so se sia il migliore in assoluto, ma ho avuto una buona esperienza con Boyer-Moore .


Conosci un modo per combinare la cattiva tabella dei turni di Boyer-Moore con Two-Way? Glibc ne fa una variante per gli aghi lunghi (> 32 byte) ma controlla solo l'ultimo byte. Il problema è che la modalità bidirezionale deve cercare la parte destra dell'ago da sinistra a destra, mentre il cattivo spostamento di Boyer-Moore è più efficace quando si cerca da destra a sinistra. Ho provato a usarlo con da sinistra a destra in modalità bidirezionale (avanzamento per tabella dei turni o normale mancata corrispondenza della metà destra a due vie, a seconda di quale sia il più lungo), ma nella maggior parte dei casi ho avuto un rallentamento del 5-10% rispetto al normale a due vie e impossibile trovare casi in cui ha migliorato le prestazioni.
R .. GitHub smette di aiutare ICE il

0

Questo non risponde direttamente alla domanda, ma se il testo è molto grande, che ne dici di dividerlo in sezioni sovrapposte (sovrapposte per una lunghezza del modello), quindi cerca simultaneamente le sezioni usando i thread. Per quanto riguarda l'algoritmo più veloce, Boyer-Moore-Horspool penso sia uno dei più veloci se non il più veloce tra le varianti di Boyer-Moore. Ho pubblicato un paio di varianti di Boyer-Moore (non conosco il loro nome) in questo argomento Algorithm più velocemente di BMH (Boyer – Moore – Horspool) Search .


0

Il più veloce è attualmente l'EPSM, di S. Faro e OM Kulekci. Vedere http://www.dmi.unict.it/~faro/smart/algorithms.php?algorithm=EPSM&code=epsm

"Exact Packed String Matching" ottimizzato per SIMD SSE4.2 (x86_64 e aarch64). Si comporta in modo stabile e ottimale su tutte le dimensioni.

Il sito che ho collegato confronta 199 algoritmi di ricerca di stringhe veloci, con i soliti (BM, KMP, BMH) piuttosto lenti. EPSM supera tutte le altre citate qui su queste piattaforme. È anche l'ultimo.

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.