KDE的Bug追踪器中有一个历史悠久的报告,几乎与某些贡献者年龄相仿:Bug 342056,标题是“文件复制极度缓慢(大量小文件)”,由Alexander Nestorov在2014年提交。报告直言不讳:用KDE复制一个包含约300万个小文件、总容量15GB的文件夹,需要5到10小时,而使用rsync仅需约20分钟。有评论者一针见血地总结道:
“对于超过几个文件的操作,我通常用cp和rsync,而不是Dolphin。”
这个Bug一直萦绕在我脑海中,因此本文旨在彻底解决它。我一直在优化KIO的复制路径,接下来将详细剖析时间都消耗在了哪里,解释背后的历史原因,并展示不同KIO版本与普通cp命令的性能对比。
任何优化前,先进行测量:

上图展示了KIO 6.28复制5000个小文件时,阻塞时间的分布情况——不存在单一热点。每个文件在阻塞时都会触发一系列短系统调用:读取挂载表(/proc/self/mountinfo)、打开源文件和目标文件,并通过内部socket向工作进程(worker)传递命令并等待响应。红色高亮的帧正是这些socket往返(发送、接收以及等待响应),约占此处阻塞时间的15%,均匀地分散在两个线程中,因为成千上万的文件都需要经历这个过程。绿色帧则是复制过程中无法避免的真实文件系统操作:statx和openat调用、copy_file_range传输数据,以及底层的ext4元数据更新。正是这种每个文件引发的系统调用风暴(而非单个调用)才是本文讨论的核心。(Off-CPU火焰图基于sched:sched_switch,按阻塞切换次数加权;点击可查看完整SVG。)
KIO是KDE软件中几乎所有文件操作背后的基础设施,从Dolphin的复制粘贴,到网络透明性——让你像打开本地文件一样打开sftp://或smb://网址。它的设计可以追溯到KDE 2时代(约2000年)。当时,解决“如何在不冻结UI的情况下执行I/O”的方案不是线程,而是独立的进程:针对某个协议启动一个工作进程(我们曾称之为kioslave),通过socket与应用程序通信。这早于Linux上可用的线程技术(Native POSIX Thread Library直到2003年才在Linux 2.6中落地),因此进程加socket IPC是当时可移植且健壮的选择,对于网络协议至今仍然如此:如果smb://的工作进程崩溃,文件管理器不会随之崩溃。
但代价也很明显:每个请求及其响应都需要序列化并通过socket传递,两端都要经过事件循环。对于file://协议,这种开销毫无意义,因为另一端是没有不可信网络的,只有本地磁盘。
2022年,David Faure改变了这一切:“使用线程实现KIO工作进程在进程内运行”合并到了Frameworks 5.95中。从此,file工作进程以线程形式运行在应用程序内部,而不是作为独立进程(其他协议则保持进程外运行以保证健壮性)。这消除了进程启动和上下文切换的成本。
然而,还有一个隐藏已久的问题:即使进程内运行,工作线程和应用程序之间仍然通过一对socket进行通信,每个命令都被序列化,仿佛它们是独立的进程。一旦使用了线程,这个socket就变成了纯粹的开销。
这正是第一张火焰图中红色高亮的部分。
因此,现在进程内的工作进程使用真正的内存传输层替代了回环socket。新引入的ThreadConnectionBackend处理命令,对于读取操作,实际数据缓冲区直接传递给对等线程,无需序列化,也无需系统调用,使得进程内文件读取达到零拷贝。这是下面性能数据中最大的一次跳跃,该改动已合并到6.29的主分支(kio!2279)。
上述改进使每个命令的成本变得低廉。但剩余的问题在于命令的数量仍然过多:复制N个文件需要执行N个独立的file_copy任务,每个任务都要经过任务调度器,并在工作进程之间进行一次往返。对于大量小文件,这种固定的每文件成本(而非数据本身)占据了主导地位。
现在,CopyJob可以将一批纯本地到本地文件一次性分派给工作进程,由工作进程统一处理(使用copy_file_range,在支持reflink的文件系统如Btrfs或XFS上还会利用写入时复制)。每个文件仍然会触发进度信号、字节计数、撤销条目,以及任何工作进程无法盲目处理的情况(如目标已存在、源文件不可读、重命名或跳过决策)——这些都会被回退到正常的逐文件路径。该机制的门控设计得非常保守,确保挂载的硬盘不会因批量操作而冻结(kio!2282)。
上图为6.29版本采用批量复制后的火焰图:红色socket往返帧已完全消失。两个因素促成了这一点:前文提到的内存传输(不再有socketpair)以及批量复制将一批文件折叠成一个命令,而非每个文件一次往返。剩下的全是真正的文件系统工作:绿色帧代表复制本身——copy_file_range移动数据、openat和statx调用,以及底层的ext4元数据操作。那些宽大的非绿色平台是基准测试循环中删除上一轮文件的操作(unlink),属于清理工作而非复制过程的一部分。
在复制之前,CopyJob需要对每个源文件执行stat操作,这又是一次逐文件的往返。后续改进将把stat阶段也合并成批量操作,通过相同的内存通道进行。下表中的batch-copy-stat列就是基于此。
这两项改进目前仍在审查中,计划在6.29之后的版本中发布,因此表中的那两列只是预览,并非当前可安装的版本。
RelWithDebInfo模式构建,未启用任何sanitizer。复制目标为真实的ext4卷(因此没有reflink捷径,copy_file_range实际移动数据)。每个KIO版本都自带对应的libKIOCore和kio_file工作进程。cp -r --preserve=mode,timestamps命令。(由于原文已截取,此部分未列出完整数据表格,但上述优化已显著提升了性能。)
关注微信号:智享开源 ,及时了解更新信息。
原文链接:https://blogs.kde.org/2026/07/28/making-kio-copy-many-files-fast/
你必须 登录 才能发表评论.
| 微信捐赠 | 支付宝捐赠 |
|---|---|
![]() |
![]() |
扫码关注公众号:智享开源

还没有任何评论,你来说两句吧!