2013-05-31 16 views
7

Ho un compito in cui devo cercare le sequenze in un file. Quando faccio un'applicazione di test, ho letto il file come stringa (File.ReadAllText) e ho usato string.IndexOf per cercare una sequenza. Quando ho provato ad implementare lo stesso algoritmo con i byte (leggendo il file come array di byte e cercando array di byte nell'array di byte), ho notato che cercare byte [] in byte [] è circa 3 volte più lento di cercare stringa in stringa . Ho fatto in modo di verificarlo a fondo, e esattamente lo stesso codice, uno che usa il byte [] e l'altro usando la stringa, richiede 3 volte tanto da eseguire - come, 16s per byte vs ~ 5s per string.Velocità di ricerca del byte [] in byte [] e stringa in stringa - perché quest'ultimo è più veloce?

Per la ricerca di matrici di byte, ho usato i modi descritti qui byte[] array pattern search. Per cercare le stringhe, ho usato la funzione IndexOf integrata della classe string. Ecco una delle implementazioni di IndexOf per byte [] Ho provato:

public int IndexOf(byte[] source, byte[] pattern, int startpos = 0) 
    { 
     int search_limit = source.Length - pattern.Length; 
     for (int i = startpos; i < search_limit; i++) 
     { 
      if (source[i] == pattern[0]) 
      { 
       bool found = true; 
       for (int j = 1; j < pattern.Length; j++) 
       { 
        if (source[i + j] != pattern[j]) 
        { 
         found = false; 
         break; 
        } 
       } 
       if (found) 
        return i; 
      } 
     } 
     return -1; 
    } 

Fondamentalmente, alzando corrispondenza successiva sequenza di byte in byte matrice prende tre volte finchè alzando corrispondenza successiva sequenza di caratteri in stringa. La mia domanda è: PERCHÉ?

Qualcuno sa come .Net gestisce la ricerca di caratteri in stringa, che tipo di ottimizzazione esegue, quale algoritmo utilizza? C'è un algoritmo più veloce di quello che sto usando qui? Forse qualcuno ha un'idea di cosa sto facendo male qui in modo che ci voglia più tempo del dovuto? Non riesco davvero a capire come cercare stringa di stringa può essere 3 volte più veloce di byte [] in byte [] ...

AGGIORNAMENTO: Ho provato l'algoritmo non sicuro come suggerito. Era il seguente:

public static unsafe long IndexOfFast(byte[] Haystack, byte[] Needle, long startpos = 0) 
    { 
     long i = startpos; 
     fixed (byte* H = Haystack) fixed (byte* N = Needle) 
     { 
      for (byte* hNext = H + startpos, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++) 
      { 

        bool Found = true; 
        for (byte* hInc = hNext, nInc = N, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ; 
        if (Found) return i; 

      } 
      return -1; 
     } 
    } 
} 

la cosa strana è che in realtà si è rivelato essere due volte più lento! L'ho cambiato per aggiungere il mio Tweak personali (controllo prima lettera prima di tentare di scorrere ago) e sembra che questa società:

public static unsafe long IndexOfFast(byte[] Haystack, byte[] Needle, long startpos = 0) 
    { 
     long i = startpos; 
     fixed (byte* H = Haystack) fixed (byte* N = Needle) 
     { 
      for (byte* hNext = H + startpos, hEnd = H + Haystack.LongLength; hNext < hEnd; i++, hNext++) 
      { 
       if (*hNext == *N) 
       { 
        bool Found = true; 
        for (byte* hInc = hNext+1, nInc = N+1, nEnd = N + Needle.LongLength; Found && nInc < nEnd; Found = *nInc == *hInc, nInc++, hInc++) ; 
        if (Found) return i; 
       } 
      } 
      return -1; 
     } 
    } 

Ora, ci vuole lo stesso tempo di eseguire come quello sicuro. La mia domanda è ancora una volta - qualche idea perché? Non dovrebbe essere più veloce perché non è sicuro e funziona con i puntatori, rispetto alla sicurezza?

+0

Implementare [questo algoritmo] (http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm) per gli array di byte e testare nuovamente. – I4V

+1

Bene, la maggior parte delle funzioni di confronto delle stringhe e 'IndexOf' si riducono a una chiamata CLR interna, in genere' InternalFindNLSStringEx' o 'InternalCompareString'. Probabilmente l'implementazione CLR nativa sarà più veloce. – vcsjones

+0

OK, ora che hai una soluzione aritmetica puntatore se vuoi renderla ancora più veloce devi iniziare a pensare a quali operazioni macchina vengono effettivamente eseguite lì. Ad esempio: cos'è più veloce: spostare un byte da una parola a 32 bit quattro volte o creare quattro maschere, una con il byte in ciascuna delle quattro posizioni, considerare l'array di byte come una matrice di int e controllare ogni int per vedere se corrisponde a una qualsiasi delle tue maschere quando ANDed con la maschera? Quest'ultimo potrebbe essere un paio di cicli più veloce. Questo è il tipo di cose che le persone fanno quando ottimizzano questo algoritmo. –

risposta

3

L'algoritmo di ricerca per byte è estremamente inefficiente!

L'algoritmo di base a cui sono confrontate tutte le altre ricerche di stringhe è Boyer-Moore. Scommetto che le ricerche su stringhe .NET lo usano o una variante di esso. Esistono anche others ma l'implementazione di Boyer-Moore per byte offre prestazioni molto migliori.

Modifica: SO al salvataggio!Here is a simple C# implementation of Boyer-Moore for byte arrays

Modifica con i numeri di sincronizzazione: commenti di Eric mi ha fatto molto interessato perché ho sempre sentito le ricerche di stringhe .Net utilizzano Boyer-Moore. Ho davvero apprezzato qualcuno che in realtà sapeva di dirmi il contrario. Ha perfettamente senso dopo averlo pensato. Ho deciso di fare un po 'di tempo nella ricerca dei byte di Boyer-Moore vs Naive e ho scoperto che Eric è assolutamente corretto per un piccolo ago e un piccolo pagliaio la ricerca ingenua è più veloce di 13 volte. Ciò che mi ha sorpreso è che il punto di "pareggio" era molto più basso di quanto mi aspettassi. Boyer-Moore migliora in modo significativo con la dimensione del modello (o la dimensione dell'ago nei miei tempi), quindi più grande è il modello che stai cercando più velocemente si cerca. Per un ago da 8 byte, la ricerca Naive vs Boyer-Moore sono state collegate alla ricerca attraverso un pagliaio da 500-600 byte. Per un pagliaio più grande, Boyer-Moore migliora sensibilmente soprattutto con un ago più grande.Per un pagliaio da 20KB e un ago da 64 byte, Boyer-Moore era 10 volte più veloce.

I numeri completi sono sotto per chiunque sia interessato.

Tutti i test utilizzavano il semplice Boyer-Moore collegato sopra e l'algoritmo di ricerca dei byte naive dell'Op che ha pubblicato facendo delle iterazioni di ricerca 1M.

1000000 iterations, haystack size = 20 bytes, needle size = 8 bytes 
20ms total : Naive Search 
268ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 600 bytes, needle size = 8 bytes 
608ms total : Naive Search 
582ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 8 bytes 
2011ms total : Naive Search 
1339ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 32 bytes 
1865ms total : Naive Search 
563ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 2000 bytes, needle size = 64 bytes 
1883ms total : Naive Search 
466ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 8 bytes 
18899ms total : Naive Search 
10753ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 32 bytes 
18639ms total : Naive Search 
3114ms total : Boyer-Moore Search 

1000000 iterations, haystack size = 20000 bytes, needle size = 64 bytes 
18866ms total : Naive Search 
1807ms total : Boyer-Moore Search 
+0

Estremamente? Al link ho fornito è uno dei più veloci. Puoi elaborare cosa sto sbagliando con il mio algoritmo? – Istrebitel

+0

Non è necessario controllare ogni byte per l'inizio del pattern. Con un po 'di preelaborazione dei tuoi byte di pattern puoi saltare in avanti nell'array di ricerca. Boyer-Moore migliora in efficienza con modelli più lunghi, in particolare perché puoi saltare più array di ricerca. – Kevin

+0

Hmm, vedo ora. Boyer-Moore è un vecchio algoritmo, non dovrebbe esserci un'implementazione ben nota per C# per byte []? Voglio dire, potrei provare a scriverlo da solo, ma non voglio inventare la ruota. – Istrebitel

11

Fondamentalmente, alzando corrispondenza successiva sequenza di byte in byte matrice richiede tre volte finchè alzando corrispondenza successiva sequenza di caratteri in stringa. La mia domanda è: PERCHÉ?

Poiché l'algoritmo di ricerca stringa è stato fortemente ottimizzato; è scritto in un codice ristretto non gestito che non passa il tempo a controllare i limiti dell'array. Se si dovesse ottimizzare allo stesso modo l'algoritmo di ricerca dei byte, sarebbe altrettanto veloce; l'algoritmo di ricerca stringa utilizza lo stesso algoritmo naive che stai utilizzando.

L'algoritmo va bene - questa è la ricerca "naive" standard, e nonostante le affermazioni di Kevin, l'algoritmo ingenuo è in pratica quasi sempre il più veloce su dati tipici. Scoppiare attraverso un array alla ricerca di un byte è incredibilmente veloce su hardware moderno. Dipende dalla dimensione del tuo problema; se stai cercando una lunga stringa di DNA nel mezzo del genoma umano, allora Boyer-Moore è totalmente degno della spesa per la pre-elaborazione. Se stai cercando 0xDEADBEEF in un file di venti KB, non hai intenzione di battere l'algoritmo ingenuo se è implementato in modo efficiente.

Per la massima velocità, ciò che è necessario fare qui è spegnere il sistema di sicurezza e scrivere il codice utilizzando l'aritmetica del puntatore non sicuro.

+0

Pensavo che ti interessassero i numeri di temporizzazione che ho aggiunto. Sicuramente convalida che la ricerca ingenua è molto meglio per "dati tipici" che cercano attraverso una piccola quantità di dati. – Kevin

Problemi correlati