Как сканировать действительно огромные файлы на диске? - PullRequest
14 голосов
/ 31 января 2010

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

Моя мысль:

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

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

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

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

спасибо.

Ответы [ 8 ]

10 голосов
/ 31 января 2010

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

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

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

5 голосов
/ 31 января 2010

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

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

4 голосов
/ 10 февраля 2010

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

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

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

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

Вот код, который я использовал, это однопотоковая версия, я использовал 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 голосов
/ 04 февраля 2010

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

1 голос
/ 04 февраля 2010

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

0 голосов
/ 06 февраля 2010

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

0 голосов
/ 04 февраля 2010

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

0 голосов
/ 04 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...