Подтвердить что ты не робот

Как сканировать действительно огромные файлы на диске?

Учитывая действительно огромный файл (возможно, более 4 ГБ) на диске, я хочу просканировать этот файл и вычислить время определенного двоичного шаблона.

Моя мысль:

  • Использовать файл с отображением памяти (CreateFileMap или boost mapped_file), чтобы загрузить файл в виртуальную память.

  • Для каждой 100 МБ сопоставленной памяти создайте один поток для сканирования и вычисления результата.

Возможно ли это? Есть ли лучший способ сделать это?

Обновление:
Файл с отображением памяти был бы хорошим выбором, для сканирования через 1,6 ГБ файл можно было бы обрабатывать в течение 11 секунд.

спасибо.

4b9b3361

Ответ 1

Многопоточность только сделает это медленнее, если вы не хотите сканировать несколько файлов с каждым на другом жестком диске. В противном случае вы просто будете искать.

Я написал простую тестовую функцию, используя файлы с отображением памяти, причем один поток для файла 1,4 Гб занял около 20 секунд для сканирования. С двумя потоками, каждый из которых занимает половину файла (даже 1 Мбайт кусков в один поток, нечетный к другому), потребовалось более 80 секунд.

  • 1 поток: 20015 миллисекунд
  • 2 потока: 83985 миллисекунд

Правильно, 2 потока были Четыре раза медленнее, чем один поток!

Здесь код, который я использовал, это однопоточная версия, я использовал шаблон с 1 байтовым сканированием, поэтому код для поиска совпадений, которые пересекают границы границ, не проверен.

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;
}

Ответ 2

Создание 20 потоков, каждый из которых предполагает обработку около 100 МБ файла, скорее всего, только ухудшит производительность, так как HD придется читать из нескольких несвязанных мест одновременно.

Производительность HD находится на пике, когда он считывает последовательные данные. Поэтому, предполагая, что ваш огромный файл не фрагментирован, лучше всего будет использовать только один поток и прочитать от начала до конца куски нескольких (скажем, 4) МБ.

Но что я знаю. Файловые системы и кеши являются сложными. Проведите некоторое тестирование и посмотрите, что лучше всего работает.

Ответ 3

Хотя вы можете использовать сопоставление памяти, вам не нужно. Если вы читаете файл последовательно в маленьких кусках, скажем по 1 МБ каждый, файл никогда не будет присутствовать в памяти сразу.

Если ваш поисковый код на самом деле медленнее вашего жесткого диска, вы все равно можете отделить куски от рабочих потоков, если хотите.

Ответ 4

У меня был бы один поток, который прочитал бы файл (возможно, как поток) в массив и обработал бы другой поток. Я бы не набрал несколько за один раз из-за попыток диска. У меня, вероятно, был бы ManualResetEvent, чтобы рассказать мой поток, когда следующий? байты готовы к обработке. Предполагая, что ваш код процесса быстрее, чем hdd, у меня будет 2 буфера, один для заполнения, а другой для обработки и просто переключаться между ними каждый раз.

Ответ 5

Я бы пошел только с одним потоком не только для проблем с производительностью HD, но и потому, что у вас могут возникнуть проблемы с управлением побочными эффектами при разделении файла: что, если возникнет ваш шаблон прямо там, где вы разделили файл?

Ответ 6

Использование файла с отображением памяти имеет дополнительное преимущество: избегать копирования из кэш-памяти файловой системы в управляемую память приложения, если вы используете просмотр только для чтения (хотя вам нужно использовать указатели байтов *, чтобы получить доступ к памяти). Вместо того, чтобы создавать много потоков, используйте один поток для последовательного сканирования файла, используя, например, 100 МБ памяти, отображаемые в файл (не сопоставляйте весь файл в адресное пространство процесса сразу).

Ответ 7

Я бы сделал это с асинхронными чтениями в двойной буфер. Поэтому, когда один буфер был прочитан из файла, начните читать следующий буфер при сканировании первого буфера. Это означает, что вы выполняете CPU и IO параллельно. Другим преимуществом является то, что у вас всегда будут данные вокруг границ данных. Однако я не знаю, возможна ли двойная буферизация с отображенными в памяти файлами.

Надеюсь, что это поможет.

Привет,

Sebastiaan

Ответ 8

Тим Брей (и его читатели) подробно изучил это в своем проекте Wide Finder и Wide Finder 2. Результаты тестов показывают, что многопоточные реализации могут превзойти однопоточное решение на массивном многоядерном сервере Sun. На обычном аппаратном обеспечении ПК многопоточность не принесет вам столько, если вообще.