【问题标题】:How to scan through really huge files on disk?如何扫描磁盘上非常大的文件?
【发布时间】:2011-01-11 09:54:55
【问题描述】:

考虑到磁盘上有一个非常大的文件(可能超过 4GB),我想扫描这个文件并计算特定二进制模式出现的次数。

我的想法是:

  1. 使用内存映射文件(CreateFileMap 或 boost mapped_file) 来加载 文件到虚拟内存。

  2. 每100MB映射内存,创建一个线程扫描计算结果。

这可行吗?有更好的方法吗?

更新
内存映射文件是个不错的选择,1.6GB的文件扫描11s即可完成。

谢谢。

【问题讨论】:

  • (2) 模式可能跨越 100MB 的边界吗?如果您必须自己编写搜索算法,并且搜索字符串相当长(越长越好!),请考虑 Boyer-Moore 算法 en.wikipedia.org/wiki/…
  • @Kristen:该模式不会跨越 100MB 边界,因为该模式是位 '1'。
  • 什么是模式,真的是单组位吗?
  • 添加比 PC 处理器更多的线程不会产生任何额外的好处。相反,由于额外的开销,它实际上减慢了进程,每个 CPU 核心只有 1 个线程并在它们之间分配工作负载(可能有一个工作队列,所以每个人一次只能处理 100mb 块,这会使队列很好,并且简单地看到它如何只是一个递增的数字,但需要使用互锁函数来安全地递增它)。此外,由于共享硬盘,这可能会更慢。

标签: .net large-files memory-mapped-files


【解决方案1】:

我会通过异步读取双缓冲区来做到这一点。因此,当从文件中读取一个缓冲区时,在扫描第一个缓冲区时开始读取下一个缓冲区。这意味着您并行执行 CPU 和 IO。另一个优点是您将始终拥有围绕数据边界的数据。但是我不知道内存映射文件是否可以进行双缓冲。

【讨论】:

    【解决方案2】:

    多线程只会让这个过程变慢,除非你想扫描多个文件,每个文件都在不同的硬盘上。否则你只是去寻找。

    我使用内存映射文件编写了一个简单的测试函数,使用单线程扫描 1.4 Gb 文件大约需要 20 秒。使用两个线程,每个线程占用文件的一半(一个线程甚至 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;
    }
    

    【讨论】:

    • 问题:为什么要使用“if (pb[ii] == pbPattern[0] && 0 == memcmp(&pb[ii], pbPattern, cbPattern))”? memcmp(&pb[ii], pbPattern, cbPattern) 在比较第一个字节(如果不相等)时不会返回 false 吗?
    【解决方案3】:

    Tim Bray(和他的读者)在他的 Wide Finder ProjectWide Finder 2 中深入探讨了这一点。 Benchmark results 表明,在大型 Sun 多核服务器上,多线程实现可以优于单线程解决方案。在普通的 PC 硬件上,多线程不会给你带来太多好处,如果有的话。

    【讨论】:

      【解决方案4】:

      我会让一个线程将文件(可能作为流)读入一个数组并让另一个线程处理它。由于磁盘寻道,我不会一次映射多个。我可能会有一个 ManualResetEvent 来告诉我的线程什么时候下一个?字节已准备好进行处理。假设您的进程代码更快,那么硬盘 i 将有 2 个缓冲区,一个要填充,另一个要处理,每次都在它们之间切换。

      【讨论】:

        【解决方案5】:

        我也只使用一个线程,不仅是为了解决高清性能问题,而且因为您在分割文件时可能无法管理副作用:如果在分割文件的地方出现了您的模式怎么办?

        【讨论】:

          【解决方案6】:

          如果您使用只读视图(尽管您必须使用 byte* 指针然后访问内存),使用内存映射文件的额外好处是避免从文件系统缓存内存复制到(托管)应用程序内存)。而不是创建多个线程,而是使用一个线程顺序扫描文件,例如使用 100MB 内存映射视图到文件中(不要一次将整个文件映射到进程地址空间)。

          【讨论】:

            【解决方案7】:

            创建 20 个线程,每个线程假设处理大约 100 MB 的文件,这可能只会降低性能,因为 HD 必须同时从几个不相关的地方读取。

            HD 性能在读取顺序数据时达到顶峰。因此,假设您的大文件没有碎片化,最好的办法可能是只使用一个线程并以几 MB(比如 4)MB 的块从头到尾读取。

            但是我知道什么。文件系统和缓存很复杂。做一些测试,看看什么效果最好。

            【讨论】:

              【解决方案8】:

              虽然您可以使用内存映射,但您不必这样做。如果您以小块顺序读取文件,例如每个 1 MB,则该文件永远不会同时出现在内存中。

              如果您的搜索代码实际上比您的硬盘慢,您仍然可以根据需要将块交给工作线程。

              【讨论】:

              • 我敢说,搜索可能比从磁盘读取慢的唯一方法是针对真正的病态病例(例如,在仅包含 1,000,000 A 字符)当使用简单的搜索方法时(就像通常为 strstr() 实现的那样)。对于任何线性时间字符串搜索(如 Knuth-Morris-Pratt),磁盘 I/O 将至少慢 100 倍。
              • 是的,这就是为什么我在“如果您的搜索代码实际上更慢......”中写了“如果”和“实际上”:)
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-14
              • 1970-01-01
              • 2011-02-02
              • 1970-01-01
              • 2020-10-17
              相关资源
              最近更新 更多