【问题标题】:fastest way to crawl recursive ntfs directories in C++在 C++ 中爬取递归 ntfs 目录的最快方法
【发布时间】:2010-06-14 22:11:35
【问题描述】:

我编写了一个小型爬虫来扫描和处理目录结构。

它基于 dirent(它是 FindNextFileA 的一个小包装器) 在我的第一个基准测试中,它出奇地慢:

4500 个文件大约需要 123473 毫秒(thinkpad t60p 本地三星 320 GB 2.5" HD)。 在 123473 毫秒内找到 121481 个文件 这个速度正常吗?

这是我的代码:

int testPrintDir(std::string  strDir, std::string strPattern="*", bool recurse=true){
  struct dirent *ent;
  DIR *dir;
  dir = opendir (strDir.c_str());
  int retVal = 0;
  if (dir != NULL) {
    while ((ent = readdir (dir)) != NULL) {
      if (strcmp(ent->d_name, ".") !=0 &&  strcmp(ent->d_name, "..") !=0){
        std::string strFullName = strDir +"\\"+std::string(ent->d_name);
        std::string strType = "N/A";
        bool isDir = (ent->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) !=0;
        strType = (isDir)?"DIR":"FILE";                 
        if ((!isDir)){
             //printf ("%s <%s>\n", strFullName.c_str(),strType.c_str());//ent->d_name);
          retVal++;
        }   
        if (isDir && recurse){
             retVal += testPrintDir(strFullName, strPattern, recurse);
        }
      }
    }
    closedir (dir);
    return retVal;
  } else {
    /* could not open directory */
    perror ("DIR NOT FOUND!");
    return -1;
  }
}

【问题讨论】:

标签: c++ performance recursion directory ntfs


【解决方案1】:

在某些情况下这样的速度是正常的。首先,使用 FindFirstFileA 而不是 FindFirstFileW 会产生从 UTF-16 到 ANSI 的转换的开销。其次,如果您正在浏览操作系统尚未访问的目录,您将招致至少一次查找惩罚(对于大多数消费者硬盘驱动器而言,大约为 16 毫秒),从而将您的枚举限制在每秒远低于 100 次目录检查。如果给定驱动器上的主文件表碎片严重,情况会变得更糟。

关于文件的数量,它将更多地取决于每个目录的文件数量,而不是文件本身的数量。

【讨论】:

  • 7200 RPM 硬盘每秒旋转 120 次。因此,一个完整的旋转需要 8 毫秒多一点。由于磁头也必须移动,因此寻道时间分布在(2、10ms)之间,平均约为 6ms,再加上读取数据需要部分旋转。这与现代主流驱动器上引用的 8-9ms 读取时间一致。 SCSI 和 SATA 支持命令队列,通过保持多个请求在运行中,驱动器可以对它们进行排序,平均每次旋转可能 20 个块读取而不是 2 个。除了稍微夸大查找成本之外,您的解释是正确的。
  • @Ben Voigt:我的 ~16 ms 值来自 Tom's Hardware 硬盘驱动器图表:tomshardware.com/charts/2009-3.5-desktop-hard-drive-charts/…。从理论上讲,驱动器的性能应该比这更好,但实际上并没有。我的想法是基于几年前的图表,当时 16 毫秒是常态。现代驱动器介于我的 16 和您的 8-9 数字之间。
  • @Ben Voigt:请注意,笔记本电脑硬盘,就像 OP 正在测试的硬盘一样,要差得多:tomshardware.com/charts/2009-2.5-mobile-hard-drive-charts/…
  • 是的,OP 没有给出驱动器转速,但你说得对,7200 RPM 对于笔记本电脑来说是一个糟糕的假设。 4500 或 5400 更为常见。 OTOH,只要控制器在 AHCI 模式下运行,如果请求重叠发出,命令队列应该有一个数量级的改进。
【解决方案2】:

第一次进行递归目录爬取时,您可能应该先枚举整个当前目录,然后将完成后要访问的所有目录排入队列。这样一来,您就有可能利用 NTFS 可能执行的任何即时预读优化。

在随后的递归目录爬网中,如果目录的元数据适合系统缓存,那么无论您如何做都没有关系。

编辑:阐明您应该如何访问目录。从技术上讲,这不是广度优先搜索。

【讨论】:

  • 这在实践中不会有太大的不同。 FindXFile api 归结为 ZwQueryDirectoryFile/NtQueryDirectoryFile api (msdn.microsoft.com/en-us/library/ff567047(VS.85).aspx),它一次检索整个目录。 FindXFile APIs 已经在这个函数周围使用了一个内部缓冲区。 FindClose API 的目的是销毁所述缓冲区。
  • @Billy,在这种情况下,我的答案的第二部分开始了 :) 尽管有一个足够填充的目录,但该缓冲区不需要再次访问磁盘吗?在这种情况下,第一部分也适用:)
【解决方案3】:

可能驱动器是瓶颈。不过你可以试试:

  1. 可以优化字符串操作 - 使用 char 数组 instęad of std::string。
  2. 不需要每次递归调用都构建 strFullName。使用单个固定的字符缓冲区(即函数内的静态数组),立即修改它。
  3. 不要按值传递 strPattern!
  4. 在调试之前不要创建 strType
  5. 其他人建议在深入递归之前构建要处理的目录列表。要构建它,我建议使用单个静态数组(类似于 2.)或使用堆栈(alloca)。
  6. 文件系统使用 Unicode 存储文件名?如果是这样,使用带有 FindFirstFileWFindNextFileW 的 unicode 字符串可能会快一些。

【讨论】:

    【解决方案4】:

    在递归潜水期间,您将 DIR 句柄保持打开状态。相反,保留您遇到的目录的本地列表,并在 closedir() 之后在该循环之外处理它们。

    【讨论】:

    • 这会增加内存消耗,但与文件扫描时间关系不大,受硬盘限制。
    猜你喜欢
    • 2020-06-12
    • 1970-01-01
    • 2019-11-10
    • 1970-01-01
    • 2021-03-10
    • 2011-06-22
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    相关资源
    最近更新 更多