2010-01-31 9 views
14

Considerando un file veramente grande (forse più di 4 GB) su disco, voglio eseguire la scansione di questo file e calcolare i tempi di uno specifico pattern binario.Come eseguire la scansione di file veramente grandi su disco?

Il mio pensiero è:

  1. di file memory-mapped Usa (CreateFileMap o aumentare mapped_file) per caricare il file alla memoria virtuale.

  2. Per ogni memoria mappata da 100 MB, creare un thread per eseguire la scansione e calcolare il risultato.

È possibile? Esiste un metodo migliore per farlo?

Aggiornamento:
file mappato in memoria sarebbe una buona scelta, per scaning attraverso un file di 1,6 GB potrebbe essere gestita all'interno di 11s.

grazie.

+4

(2) È possibile che il modello si estenda per un limite di 100 MB? Se devi scrivere tu stesso l'algoritmo di ricerca e la stringa di ricerca è ragionevolmente lunga (più è lunga meglio è!), Considera l'algoritmo di Boyer-Moore http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm – Kristen

+0

@Kristen: il pattern non si estende su un limite di 100 MB, perché il pattern è bit '1'. – Jichao

+0

Qual è lo schema, è davvero un singolo bit impostato? – GalacticJello

risposta

4

Il multithreading sta solo andando a rallentare se non si desidera eseguire la scansione di più file con ciascuno su un disco rigido diverso. Altrimenti stai andando a cercare.

Ho scritto una semplice funzione di test utilizzando i file mappati in memoria, con un singolo thread un file da 1,4 GB richiedeva circa 20 secondi per la scansione. Con due thread, ognuno dei quali prendeva metà del file (anche un chilo di 1 MB in un thread, dispari rispetto all'altro), ci sono voluti più di 80 secondi.

  • 1 discussione: 20015 millisecondi
  • 2 Discussioni: 83985 millisecondi

Proprio così, 2 filetti era Quattro volte più lento di 1 filo!

Ecco il codice che ho usato, questa è la versione a thread singolo, ho usato un modello di scansione da 1 byte, quindi il codice per individuare le corrispondenze che si trovano tra i confini della mappa non è stato verificato.

HRESULT ScanForPattern(LPCTSTR pszFilename, LPBYTE pbPattern, UINT cbPattern, LONGLONG * pcFound) 
{ 
    HRESULT hr = S_OK; 

    *pcFound = 0; 
    if (! pbPattern || ! cbPattern) 
     return E_INVALIDARG; 

    // Open the file 
    // 
    HANDLE hf = CreateFile(pszFilename, 
          GENERIC_READ, 
          FILE_SHARE_READ, NULL, 
          OPEN_EXISTING, 
          FILE_FLAG_SEQUENTIAL_SCAN, 
          NULL); 

    if (INVALID_HANDLE_VALUE == hf) 
     { 
     hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 
     // catch an open file that exists but is in use 
     if (ERROR_SHARING_VIOLATION == GetLastError()) 
     hr = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); 
     return hr; 
     } 

    // get the file length 
    // 
    ULARGE_INTEGER uli; 
    uli.LowPart = GetFileSize(hf, &uli.HighPart); 
    LONGLONG cbFileSize = uli.QuadPart; 
    if (0 == cbFileSize) 
     { 
     CloseHandle (hf); 
     return S_OK; 
     } 

    const LONGLONG cbStride = 1 * 1024 * 1024; // 1 MB stride. 
    LONGLONG cFound = 0; 
    LPBYTE pbGap = (LPBYTE) malloc(cbPattern * 2); 

    // Create a mapping of the file. 
    // 
    HANDLE hmap = CreateFileMapping(hf, NULL, PAGE_READONLY, 0, 0, NULL); 
    if (NULL != hmap) 
     { 
     for (LONGLONG ix = 0; ix < cbFileSize; ix += cbStride) 
     { 
     uli.QuadPart = ix; 
     UINT cbMap = (UINT) min(cbFileSize - ix, cbStride); 
     LPCBYTE pb = (LPCBYTE) MapViewOfFile(hmap, FILE_MAP_READ, uli.HighPart, uli.LowPart, cbMap); 
     if (! pb) 
      { 
      hr = HRESULT_FROM_WIN32(GetLastError()); 
      break; 
      } 
     // handle pattern scanning over the gap. 
     if (cbPattern > 1 && ix > 0) 
      { 
      CopyMemory(pbGap + cbPattern - 1, &pb[0], cbPattern - 1); 
      for (UINT ii = 1; ii < cbPattern; ++ii) 
       { 
       if (pb[ii] == pbPattern[0] && 0 == memcmp(&pb[ii], pbPattern, cbPattern)) 
        { 
        ++cFound; 
        // advance by cbPattern-1 to avoid detecting overlapping patterns 
        } 
       } 
      } 

     for (UINT ii = 0; ii < cbMap - cbPattern + 1; ++ii) 
      { 
      if (pb[ii] == pbPattern[0] && 
       ((cbPattern == 1) || 0 == memcmp(&pb[ii], pbPattern, cbPattern))) 
       { 
       ++cFound; 
       // advance by cbPattern-1 to avoid detecting overlapping patterns 
       } 
      } 
     if (cbPattern > 1 && cbMap >= cbPattern) 
      { 
      // save end of the view in our gap buffer so we can detect map-straddling patterns 
      CopyMemory(pbGap, &pb[cbMap - cbPattern + 1], cbPattern - 1); 
      } 
     UnmapViewOfFile(pb); 
     } 

     CloseHandle (hmap); 
     } 
    CloseHandle (hf); 

    *pcFound = cFound; 
    return hr; 
} 
+0

Domanda: Perché usi "if (pb [ii] == pbPattern [0] && 0 == memcmp (& pb [ii], pbPattern, cbPattern)) "? Non memcmp (& pb [ii], pbPattern, cbPattern) restituisce falso non appena confronta i primi byte se questi non sono uguali? –

5

Sebbene sia possibile utilizzare la mappatura della memoria, non è necessario. Se si legge il file in sequenza in piccoli blocchi, ad esempio 1 MB ciascuno, il file non sarà mai presente in memoria in una sola volta.

Se il tuo codice di ricerca è in realtà più lento del tuo disco rigido, puoi comunque spostare i blocchi in thread di lavoro, se lo desideri.

+0

Immagino che l'unico modo in cui la ricerca potrebbe essere più lenta della lettura dal disco sarebbe per casi veramente patologici (ad esempio, cercando 999.999 caratteri 'A' seguiti da un' B' in un file contenente appena 1.000.000 caratteri 'A') quando si usa un metodo di ricerca ingenuo (come quello in genere implementato per 'strstr()'). Per qualsiasi ricerca di stringa in tempo lineare (come Knuth-Morris-Pratt), l'I/O del disco sarà almeno 100 volte più lento. –

+2

Sì, è per questo che ho scritto "se" e "effettivamente" in "Se il tuo codice di ricerca è effettivamente più lento ..." :) – Thomas

10

Creazione di 20 thread, ognuno dei quali suppone di gestire circa 100 MB del file rischia di peggiorare le prestazioni solo perché l'HD dovrà leggere da più posti non collegati contemporaneamente.

Le prestazioni HD sono al massimo quando vengono letti dati sequenziali. Quindi, supponendo che il tuo file enorme non sia frammentato, la cosa migliore da fare sarebbe probabilmente quella di usare solo un thread e leggere dall'inizio alla fine in blocchi di pochi (diciamo 4) MB.

Ma cosa so. I file system e le cache sono complessi. Fai qualche prova e vedi cosa funziona meglio.

0

L'utilizzo di un file mappato in memoria ha l'ulteriore vantaggio di evitare una copia dalla memoria cache del file system alla memoria dell'applicazione (gestita) se si utilizza una vista di sola lettura (anche se è necessario utilizzare i puntatori byte * per accedere al memoria). E invece di creare molti thread usa un thread per scansionare in sequenza il file usando per esempio 100MB viste mappate in memoria nel file (non mappare l'intero file nello spazio di indirizzamento del processo in una sola volta).

0

Lo farei con letture asincrone in un doppio buffer. Quindi, quando un buffer è stato letto dal file, inizia a leggere il buffer successivo durante la scansione del primo buffer. Ciò significa che si fanno CPU e IO in parallelo. Un altro vantaggio è che avrai sempre dati attorno ai limiti dei dati. Tuttavia non so se è possibile il doppio buffering con i file mappati in memoria.

Spero che questo aiuti.

saluti,

Sebastiaan

1

mi piacerebbe andare con un solo filo troppo, non solo per i problemi di prestazioni HD, ma perché si potrebbe avere difficoltà a gestire gli effetti collaterali, quando a dividere il file: cosa succede se c'è un occorrenza del tuo pattern proprio dove hai diviso il tuo file?

2

Vorrei che un thread legga il file (possibilmente come un flusso) in un array e che un altro thread lo elabori. Non vorrei mapparne più in una volta a causa della ricerca del disco. Probabilmente avrei un ManualResetEvent per raccontare la mia discussione quando la prossima? i byte sono pronti per essere elaborati. Supponendo che il tuo codice di processo sia più veloce allora l'hdd avrò 2 buffer, uno da riempire e l'altro da elaborare e passare ogni volta tra loro.

0

Tim Bray (e i suoi lettori) ha esplorato questo in profondità nel suo Wide Finder Project e Wide Finder 2. Benchmark results mostra che le implementazioni multithreading possono superare una soluzione a thread singolo su un enorme server multicore Sun. Sul solito hardware del PC, il multithreading non ti farà guadagnare molto, se non del tutto.

Problemi correlati