You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TreeSize Free为何能快速计算文件夹大小?C++实现优化探讨

为什么TreeSize Free计算文件夹速度远超C++标准库实现?

TreeSize Free是一款可列出目录内文件夹、按文件大小降序排序的工具,能帮用户找出占用空间最大的文件/文件夹以清理硬盘。我好奇它为何速度如此之快,自己用C++标准库写了一段计算文件夹大小的代码:

size_t calculate_directory_size(const std::filesystem::path& directory, const size_t maximum_size)
{
    size_t size = 0;
    for (std::filesystem::recursive_directory_iterator directory_iterator(directory);
        directory_iterator != std::filesystem::recursive_directory_iterator(); ++directory_iterator)
    {
        if (!is_directory(*directory_iterator))
        {
            size += file_size(*directory_iterator);
        }

        if (size > maximum_size)
        {
            // Save time
            return size;
        }
    }

    return size;
}

即便开了优化构建,这段代码的速度还是比TreeSize Free慢3-4倍。有没有比当前实现更高效的迭代和文件大小求和技巧?另外我觉得多线程不会带来这么大的性能提升,毕竟磁盘访问很难靠多线程大幅提速。


优化方案解析

TreeSize Free之所以快,核心是它没有依赖C++标准库的通用实现,而是直接调用了Windows原生API(比如FindFirstFileExW、GetFileSizeEx,甚至更底层的NTFS相关接口),避开了标准库的额外开销。针对你的代码,有几个关键优化点:

1. 跳过标准库的冗余操作

std::filesystem的迭代器每次迭代都会做很多兼容性检查和抽象层处理,比如跨平台路径解析、权限验证等。直接用Windows原生API遍历目录,可以减少这些额外开销:

  • 用FindFirstFileExW开启FINDEX_INFO_LEVELS::FindExInfoBasic模式,只获取必要的文件信息,避免读取冗余属性;
  • 遍历过程中直接从WIN32_FIND_DATAW结构体里提取文件大小,不用单独调用file_size(这会额外发起一次系统调用)。

2. 利用NTFS的特性加速

如果目标磁盘是NTFS格式,可以直接读取MFT(主文件表)相关信息,不用逐个遍历文件:

  • 调用DeviceIoControl配合FSCTL_GET_NTFS_VOLUME_DATA获取卷信息;
  • 用FSCTL_GET_RETRIEVAL_POINTERS直接读取文件的簇信息,计算实际占用空间(注意:文件大小和实际占用磁盘空间可能不同,TreeSize会区分这两个值)。

3. 批量处理与缓存优化

  • 一次性读取多个目录项,减少系统调用次数;
  • 利用操作系统的文件系统缓存,优先遍历内存中已缓存的目录,避免重复磁盘IO;
  • 你的代码里每次判断size > maximum_size就返回,但可以提前预判:如果当前目录的已统计大小加上该目录的预估大小(比如从目录元数据获取)已经超过阈值,直接跳过整个子目录。

4. 多线程的正确用法

你说多线程没用其实不准确——机械硬盘确实很难靠多线程提速,但SSD可以并行处理多个IO请求。TreeSize会在遍历不同子目录时用多线程并行处理,因为不同子目录的文件分布在磁盘的不同位置,SSD可以同时读取这些数据,大幅提升速度。

优化后的示例思路(伪代码)

#include <windows.h>

ULONGLONG calculate_directory_size(const wchar_t* directory, ULONGLONG maximum_size)
{
    ULONGLONG total_size = 0;
    WIN32_FIND_DATAW find_data;
    HANDLE find_handle = FindFirstFileExW(directory, FindExInfoBasic, &find_data, FindExSearchNameMatch, nullptr, 0);

    if (find_handle == INVALID_HANDLE_VALUE) return 0;

    do {
        if (wcscmp(find_data.cFileName, L".") == 0 || wcscmp(find_data.cFileName, L"..") == 0)
            continue;

        if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            wchar_t sub_dir[MAX_PATH];
            swprintf_s(sub_dir, L"%s\\%s", directory, find_data.cFileName);
            total_size += calculate_directory_size(sub_dir, maximum_size - total_size);
            if (total_size > maximum_size) break;
        }
        else
        {
            ULONGLONG file_size = (ULONGLONG)find_data.nFileSizeHigh << 32 | find_data.nFileSizeLow;
            total_size += file_size;
            if (total_size > maximum_size) break;
        }
    } while (FindNextFileW(find_handle, &find_data) != FALSE);

    FindClose(find_handle);
    return total_size;
}

这段伪代码直接调用Windows原生API,避免了std::filesystem的抽象开销,同时在遍历过程中直接从WIN32_FIND_DATAW获取文件大小,减少了一次系统调用,速度会比你的原代码快很多。

内容的提问来源于stack exchange,提问作者BullyWiiPlaza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 09:15:33