Ncdu (NCurses Disk Usage) 是一个基于终端的磁盘使用分析器,用于 Linux 和其他 POSIX-y 系统,(以前)用 C 编写,在大多数发行版的包存储库中可用。在过去的几个月里,我一直致力于完全重写 ncdu,并且刚刚发布了 2.0-beta1。 Ncdu 2 是 ncdu 1.x 的完全替代品,保留了所有相同的功能、相同的 UI、键绑定和命令行标志,所有这些都使其成为合适的替代品。毕竟,没有什么比每次决定更新时都必须重新熟悉已安装的每个软件更烦人的了。当然,如果新版本至少没有改进某些东西,那么重写 ncdu 就没有任何意义。这次改写,其实是我苦恼了很久的ncdu 1.x的几个主要缺点的结果: 一直有点内存占用。这部分是它所做的工作所固有的:分析完整的目录树并使其可顺利浏览需要跟踪该树中的每个节点,并且大型目录包含数百万个文件......是的,这需要一些内存。当然,我已经实现了所有容易实现的成果,以确保 ncdu 的内存不会过于低效,但肯定还有改进的空间。 Ncdu 1.x 不能非常有效地处理硬链接计数,并且在某些(幸运的是很少见)情况下会陷入 O(n²) 循环。在不增加内存使用的情况下修复这个问题特别麻烦。另一个与硬链接相关的问题:硬链接在目录的累积大小中只计算一次,这与大多数其他支持硬链接的磁盘使用分析器所做的一致。这很有用,因为通过硬链接“复制”的文件只在磁盘上真正出现一次,文件的数据实际上并没有被复制。但与此同时,此功能可能会产生误导:删除目录不一定会回收 ncdu 指示的磁盘空间,因为该目录外的硬链接可能指向相同的文件数据,因此数据保留在磁盘上。关于如何更好地呈现此类场景,我有一个好主意,但我始终无法实现有效的实现。上述问题在 ncdu 1.x 的数据模型中都不容易解决,甚至不可能解决,因此需要对核心数据模型进行重大更改。由于 ncdu 的每个方面——UI 和所有算法——都与数据模型密切相关,这实际上归结为完全重写。正是在穿越当地森林的徒步旅行中,我终于找到了一个解决所有三个问题的有希望的解决方案。
抱歉。我很想尝试我的“有希望的解决方案”,而 C 并不是那种使快速原型设计变得非常容易或令人愉快的语言。所以我最终使用 Zig 进行原型设计,它最终不仅仅是一个原型。 Zig 是一种优秀的编程语言,非常适合像 ncdu 这样的小工具,但它目前有一个主要缺陷:它甚至还没有接近稳定。该语言、标准库和编译器都没有稳定版本,而且每个新版本都存在左右重大缺陷。它也有相当多的(已知的)错误和它的可移植性故事,虽然该语言所处的阶段令人印象深刻,但还没有与 C 相提并论。该语言看起来非常有前途,我毫不怀疑 Zig 最终会达到稳定性和可移植性水平使其成为 ncdu 的良好目标。但是,从局外人的角度来看,这可能还需要几年时间。没关系,毕竟每种语言都需要时间来成熟。但这对 ncdu 意味着什么?对于普通用户来说,可能没有那么多。我像往常一样为 Linux 提供静态二进制文件,因此您可以像往常一样获取这些文件并运行花哨的新 ncdu。如果你想从源代码编译,你只需要获取正确的 Zig 版本并运行 zig build。假设您没有遇到错误,也就是说,在大多数情况下,事情往往开箱即用。对于发行版来说,Zig 的情况更成问题,主要是因为 ncdu 的版本现在与 Zig 的版本紧密相关,这反过来意味着发行版无法相互独立地升级这些包。如果他们打包 Zig 8.0,那么他们可能还需要打包一个可以使用 Zig 8.0 编译的 ncdu 版本。如果他们想升级到更新版本的 Zig,他们可能无法在不等待我发布适用于该特定 Zig 的新版本或维护他们自己的 ncdu 本地补丁的情况下这样做。另一种选择是发行版必须同时支持多个版本的 Zig,但很少有人有时间和基础设施来做到这一点。任何一种解决方案都是混乱的,为此我深表歉意。综上所述,只要有人用,我就会继续维护ncdu的C版。维护几乎意味着我过去几年一直在做的事情:在开发方面不是特别活跃,只是偶尔的改进和修复。我还可能将未来 2.x 版本中的一些新增内容向后移植回 C 版本,特别是关于可见界面(CLI 标志、键绑定、UI 等),以抑制在碰巧有的系统之间切换时出现的不可避免的痛苦安装了不同的版本,但在 1.x 代码库中实现起来似乎很麻烦的功能可能仍然是 Zig 版本独有的。只要 Zig 还没有稳定版本,我将尝试使 ncdu 2.x 与大多数发行版都有软件包的 Zig 版本保持最新,这实际上通常意味着最新的标记版本。从好的方面来说,ncdu 2.0 只需要 Zig 编译器(加上它附带的标准库)和 ncurses。没有其他外部依赖项,也没有那些困扰用其他花哨的新语言编写的项目的销售、捆绑和疯狂的包管理东西。 1 Ncdu 2 在普通场景中使用不到一半的内存,但如果有很多硬链接,在某些情况下可能会使用更多的内存。与几个真实世界的目录进行快速比较:
我必须在这里声明一个免责声明,我的桌面根文件系统和我的备份都发挥了 ncdu 2.0 的优势:每个目录的文件数量相对较多,平均约 10 个字节,文件名相当短。尽管如此,如果您的目录树遵循不同的发行版,您仍然应该看到显着的改进。这里最大的例外是当你有很多硬链接时。我上面测试的“许多硬链接”目录代表了大约 43k 文件的基于硬链接的增量备份,“复制”了 30 次。当您查看表示树中的每个节点需要多少字节时,内存使用方面的这些差异的原因就很清楚了:(这些数字假定为 64 位指针,不包括文件名的存储以及内存分配和哈希表的开销。扩展模式 (-e) 在两个版本中每个节点使用额外的 18 个字节,并且两个版本对文件名使用相同的内存分配策略。)虽然在硬链接情况下有改进的空间,但 ncdu 1 中的性能问题如果不增加内存,我之前提到的 .x 就无法真正修复。我一直对接受完全禁用硬链接检测的选项持谨慎态度,因为结果可能不是很有用,但也许我会在未来的版本中重新考虑这一点。由于内存不足而根本无法分析的目录也不是很有用。另一个值得一提的区别是:当从浏览器中刷新目录时,ncdu 1.x 将为新树分配一个新结构,然后在扫描完成后释放旧结构。如果刷新最顶层目录,这可能会导致 ncdu 1.x 临时使用两倍的内存。 Ncdu 2.0 而是对现有的内存树进行就地更新,从而避免了这种重复。另一方面,ncdu 2.0(当前)无法重用已重命名或删除的树节点,因此频繁刷新具有许多重命名或删除的目录会随着时间的推移增加内存使用。我不认为这是一个很常见的情况,但如果它成为一个问题,它可以被修复。正如我在介绍中提到的,计算硬链接可能会非常混乱,因为它们会导致数据在目录之间共享。因此,与其尝试将目录的累积大小显示为单个数字,不如用单独的列来更好地表示这些情况。这是 ncdu 2.0 中的样子:据我所知,没有其他磁盘使用分析器具有此功能(但如果我错了,请纠正我!)
目前,您可以通过按“u”在两个视图之间切换。但是,如果我继续为每个新功能分配密钥,我可能很快就会用完可用的密钥,所以也许我会在稳定的 2.0 版本之前收回该密钥,并改为实施快速配置菜单。此功能确实带有一个很大的免责声明:如果特定文件的链接计数在扫描期间发生更改,或者目录刷新或删除导致缓存链接计数发生更改,则显示的共享/唯一大小将不正确。发生这种情况时获得正确大小的唯一方法是退出 ncdu 并开始新的扫描,从浏览器刷新不会修复它。当发生这种情况时,目前没有任何指示或警告,需要在我进行稳定版本之前修复。作为重写的一部分,ncdu 2 中还有许多其他变化。有些变化是好的,有些则可能不太好。改进了对 Unicode 文件名的处理。它仍然不处理 Unicode 组合标记,但至少它现在可以识别全角字符,并且不会在 UTF-8 序列的中间截断文件名。由于缓存 statfs() 调用的结果,提高了使用 --exclude-kernfs 时的性能。我尝试对其进行测量,但最多只注意到了大约 2% 的改进,但确实如此。在硬链接信息窗口的“链接”选项卡中,现在可以直接跳转到所选路径。文件浏览器现在可以更好地记住切换目录时所选项目在屏幕上的位置。
Ncdu 2.0 不再适用于非 UTF-8 语言环境,但我认为现在这不会成为问题。它仍然可以很好地处理非 UTF-8 文件名,但是这些将在输出之前被转义,而不是像 ncdu 1.x 那样直接抛出到您的终端。项目信息窗口组织略有不同。只是一点点,我保证。 Ncdu 2 现在使用 openat() 系列系统调用来扫描目录。这通常是对 ncdu 1.x 的 chdir() 和 opendir() 方法的改进,但确实需要更多的文件描述符(大问题)并且对古代系统的可移植性较差(Zig 甚至可以在这些系统上工作?)。在硬链接的信息窗口中打开“链接”选项卡现在需要扫描内存树,因此速度明显变慢。然而,令我惊讶的是,在我的系统上完全扫描具有 30 多万个文件的树只需要不到一秒钟的时间,因此实际上这可能不会成为问题(无论如何谁使用了“链接”选项卡?)。刷新或删除目录时,浏览器 UI 不再可见。问题是浏览器会缓存有关打开目录的信息,并且在运行刷新/删除时该缓存可能会失效。这也是 ncdu 1.x 中的一个问题,但不那么明显。有人要求在 ncdu 仍在扫描时允许交互式浏览,所以如果我有时间实现它,这不会成为问题,但这对我来说并不是什么优先事项。在每次 UI 绘制时更新浏览器的缓存成本太高,所以我还不确定如何处理。给自己拿一个 ncdu-2.0-beta1 并进行测试!源代码可在存储库的“zig”分支中找到。我的首要任务是让 2.0 为“稳定”版本做好准备,这意味着它需要在野外进行一些严肃的测试,以评估它的工作情况并充实不可避免的错误。我仍然有点不清楚,当它所建立的基础本质上不稳定时,发布稳定版本是否有意义,但让我们看看事情的进展。
从更长远的角度来看,对 Zig 的重写为更多我一直想看到的功能开辟了可能性,但这在 1.x 中实现起来似乎很棘手。多线程扫描。这对于老式的旋转硬盘驱动器是无用的,但是对于 SSD,尤其是 NVMe,通过跨多个线程分配工作可以大大提高扫描性能。在重写代码时,我想出了一个关于如何实现这一点的有前途的想法,所以我很乐意在未来的版本中进行试验(io_uring 也是一个有趣的目标,但可能更复杂)。更快的 --exclude-pattern 匹配。老实说,这个功能目前在两个版本中都太慢了,我很惊讶没有人抱怨过它(无论如何对我来说不是)。只需要几个模式就可以将 ncdu 的扫描性能降低到爬行,更聪明的匹配实现可以提供重大改进。将内存树导出到文件。 Ncdu 已经有导出/导入选项,但导出需要一个单独的命令调用 - 当前无法在目录浏览器中编写导出。新的数据模型可以支持此功能,但我仍然不确定如何在 UI 中使用它。透明导出压缩。导出函数转储未压缩的 JSON 数据,旨在通过 gzip 或类似命令进行管道传输。虽然这在手册页中有记录,但我仍然看到很多人将导出写入磁盘而没有任何压缩。这是一个相当大的空间浪费,所以如果 ncdu 可以通过外部(解)压缩工具透明地运行导出的数据以使其更容易和更容易发现,那就太好了。这些功能是我过去十年来一直致力于进行的一长串其他可能改进的补充,所以不要期望太多。 :) 这并不是说 Zig 不受使用数百个微小依赖项的项目问题的影响,但是在当前状态下,这种开发风格并没有受到强烈鼓励:标准库已经涵盖了很多领域,包管理解决方案仍然存在正在开发中,使用现有的 C 库很容易。 ↩
我绝对希望像这样的目录级报告可用于其他形式的数据共享,例如 reflinks 或 btrfs/ZFS 快照。但是,唉,我怀疑我是否能够在 ncdu 中实现它。即使我能以某种方式抓取和解开底层数据,跟踪大型文件系统中的每个块无疑在 CPU 和内存方面都会非常昂贵。前段时间我确实写了一个小工具来为启用配额的 btrfs 子卷生成此类报告,但后来我最终禁用了配额功能,因为即使这样也非常昂贵。还有 btdu,它采用一种非常有趣的方法来分析 btrfs 文件系统。 ↩