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

StackOverflow https://stackoverflow.com/questions/2171625

Вопрос

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

Моя мысль:

  1. Используйте память-сопоставленный файл (CreateFileMap или Boost Maped_file), чтобы загрузить файл в виртуальную память.

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

Это осуществимо? Есть ли лучший метод, чтобы сделать это?

Обновлять:
Спопущенный память файл будет хорошим выбором, для сканирования через файл 1.6GB можно обрабатывать в течение 11 секунд.

Благодарю.

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top