14

ディスク上の非常に巨大なファイル(おそらく4GB以上)を考慮して、このファイルをスキャンして、特定のバイナリパターンが発生する時間を計算したいと思います。

私の考えは:

  1. メモリマップトファイル(CreateFileMapまたはブーストmapped_file)を使用して、ファイルを仮想メモリにロードします。

  2. 100MBのマップトメモリごとに、スキャンして結果を計算するスレッドを1つ作成します。

これは実行可能ですか?そうするためのより良い方法はありますか?

更新
1.6GBファイルのスキャンは11秒以内に処理できるため、メモリマップトファイルが適しています。

ありがとう。

4

8 に答える 8

10

20 個のスレッドを作成し、それぞれが約 100 MB のファイルを処理することを想定すると、HD は関連のないいくつかの場所から同時に読み取る必要があるため、パフォーマンスが低下する可能性があります。

HD のパフォーマンスは、シーケンシャル データの読み取り時にピークに達します。したがって、巨大なファイルが断片化されていないと仮定すると、おそらく最善の方法は、スレッドを 1 つだけ使用し、最初から最後まで数 MB (たとえば 4 MB) のチャンクで読み取ることです。

しかし、私は何を知っていますか。ファイル システムとキャッシュは複雑です。いくつかのテストを行い、何が最適かを確認してください。

于 2010-01-31T12:32:55.357 に答える
5

メモリマッピングを使用できますが、使用する必要はありません。ファイルを小さなチャンク(たとえば、それぞれ1 MB)で順番に読み取る場合、ファイルが一度にメモリに存在することはありません。

検索コードが実際にハードディスクよりも遅い場合でも、必要に応じてチャンクをワーカースレッドに渡すことができます。

于 2010-01-31T12:21:11.253 に答える
4

マルチスレッドは、異なるハードドライブ上のそれぞれで複数のファイルをスキャンする場合を除いて、これを遅くするだけです。そうでなければ、あなたはただ求めるつもりです。

メモリマップトファイルを使用して簡単なテスト関数を作成しました。シングルスレッドでは、1.4Gbファイルのスキャンに約20秒かかりました。2つのスレッドがあり、それぞれがファイルの半分を使用している場合(1つのスレッドに対して1MBのチャンクでさえ、他のスレッドに対しては奇数)、80秒以上かかりました。

  • 1スレッド:20015ミリ秒
  • 2スレッド:83985ミリ秒

そうです、2つのスレッドは1つのスレッドよりも4倍遅くなりました!

これが私が使用したコードです。これはシングルスレッドバージョンです。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;
}
于 2010-02-10T05:33:31.193 に答える
2

1 つのスレッドでファイルを (おそらくストリームとして) 読み取って配列に入れ、別のスレッドでそれを処理します。ディスクシークのため、一度に複数をマップすることはありません。私はおそらく ManualResetEvent を使用して、スレッドに次のタイミングを通知しますか? バイトを処理する準備ができています。プロセスコードが hdd よりも高速であると仮定すると、2 つのバッファがあり、1 つは充填し、もう 1 つは処理し、毎回それらを切り替えるだけです。

于 2010-02-04T20:52:42.707 に答える
1

HDパフォーマンスの問題だけでなく、ファイルを分割する際の副作用の管理に問題がある可能性があるため、スレッドも1つだけにします。ファイルを分割した場所でパターンが発生した場合はどうなりますか?

于 2010-02-04T12:56:38.687 に答える
0

Tim Bray (および彼の読者) は、彼のWide Finder ProjectおよびWide Finder 2でこれを詳細に調査しました。ベンチマークの結果は、大規模な Sun マルチコア サーバーでは、マルチスレッドの実装がシングルスレッドのソリューションよりも優れていることを示しています。通常の PC ハードウェアでは、マルチスレッド化によるメリットは、たとえあったとしてもそれほど多くはありません。

于 2010-02-06T17:11:57.477 に答える
0

メモリ マップト ファイルを使用すると、読み取り専用ビューを使用する場合に、ファイルシステム キャッシュ メモリから (マネージド) アプリケーション メモリへのコピーを回避できるという追加の利点があります (ただし、メモリにアクセスするには byte* ポインターを使用する必要があります)。また、多くのスレッドを作成する代わりに、1 つのスレッドを使用して、たとえば 100MB のメモリ マップ ビューを使用してファイルを順次スキャンします (ファイル全体を一度にプロセス アドレス空間にマップしないでください)。

于 2010-02-04T11:04:01.917 に答える
0

ダブルバッファへの非同期読み取りでそれを行います。そのため、ファイルから 1 つのバッファーが読み取られたら、最初のバッファーをスキャンしながら次のバッファーの読み取りを開始します。これは、CPU と IO を並行して実行することを意味します。もう 1 つの利点は、データ境界の周りに常にデータがあることです。ただし、メモリ マップ ファイルでダブル バッファリングが可能かどうかはわかりません。

于 2010-02-04T11:25:39.173 に答える