剧集目录里,每个视频旁边都躺着两三个字幕、一张封面、一个 nfo。单看每个都小到可以忽略,凑到一起,硬盘报出来的数字能让人愣一下。更烦的是同步:一晚上过去进度条还在那儿磨,带宽明明没跑满。
这两件事其实是同一个原因。我拿一台普通机器量了一遍。
先说占地方:同一个目录,两个数对不上
我造了两万个 1KB 的字幕文件,放在一个目录里。
- 逻辑大小:20,480,000 字节,19.5MB
- 实际占用:86,376,448 字节,82.4MB
- 放大倍数:4.2 倍
把单个文件压到 100 字节,同样两万个:
- 逻辑大小:1.9MB
- 实际占用:23.8MB
- 放大倍数:12.5 倍
再换一组,五千个正好 4096 字节的文件:逻辑 19.5MB,实际 20.8MB,放大 1.06 倍。几乎不亏。
原因不复杂。NTFS 默认 4KB 一个簇,文件哪怕只有 100 字节,也要独占一整个簇。100 字节的内容躺在 4096 字节的格子里,剩下 3996 字节只能是空的,谁也放不进去。所以文件越小,浪费比例越高,4KB 是个分水岭——到 4KB 就刚好占满一格,再往上放大就按簇取整了。
除了簇,还有目录项。上面两万个 1KB 的文件,簇算下来是 81,920,000 字节,实测 86,376,448 字节,中间多出 4.46MB,除以两万个,平均每个文件多 233 字节。这是文件名、时间戳、权限这些东西的账,它跟着文件数走,不跟着内容走。
所以资源管理器里那两列不是重复设计:「大小」是内容,是 19.5MB;「占用空间」是实际吃掉的盘,是 82.4MB。NAS 面板报的通常是后者,网盘客户端算配额有时用前者,两边对不上很正常。
慢起来更没道理:同样 19.5MB,差两千多倍
占地方还能忍,慢是真的忍不了。
同一个 20MB 的单文件和一个 19.5MB、两万个文件的目录,都通过本机共享复制一遍:
- 20MB 单文件走共享:91 毫秒
- 两万个 1KB 文件走共享(单线程):206,051 毫秒
- 两万个 1KB 文件走共享(8 线程):71,613 毫秒
- 两万个 1KB 文件原地复制(单线程):24,942 毫秒
数据量基本一样,时间差了 2264 倍。
摊到每个文件上更清楚:共享单线程平均 10.3 毫秒一个,开 8 线程降到 3.6 毫秒,纯本地复制是 1.25 毫秒。多出来的 9 毫秒不是传数据的时间,是每个文件都要单独走一遍打开、取属性、关掉、写目录项这几步。文件一大,这几步被数据淹没;文件一小,全是它的开销。
这也是为什么同样一堆东西,打包成一个压缩包传,速度会突然变得像样。
网盘同步是同一笔账
本地共享还只是局域网,换成走 HTTP 上传,账一样。
我在本机起了一个收 PUT 的服务,两万个文件里取两千个来传:
- 2000 个 1KB 文件,串行上传:3,946 毫秒
- 2000 个 1KB 文件,8 并发:2,164 毫秒
- 1 个 20MB 文件:92 毫秒
注意这两千个文件加起来才 2MB,比那个 20MB 的整包小了十倍,耗时反而是它的四十多倍。单次请求的成本大概 2 毫秒,两万次就是四十秒起,而这个成本跟文件多大几乎无关。
所谓「网盘同步几十万个小文件永远跑不完」,瓶颈基本都在这。带宽空着,请求数打满,客户端还得为每个文件维护队列和状态。
打包这一步到底值不值
拿同样两万个 1KB 文件打成一个 tar 包,再传这一个包:
- 打包耗时:12,476 毫秒
- 包大小:30,730,240 字节,29.3MB
- 传这个包:93 毫秒
- 合计:约 12.6 秒
同样一批东西,散着传 206 秒,打包再传 12.6 秒。包甚至比内容还大——三十万个字节里有一半是 tar 给每个文件加的头和 512 字节对齐的填充,一个 1KB 的文件实际要占 1536 字节。多花这十兆,换回两百秒,我觉得很划算。
但别把它当日常。打包要读一遍写一遍,解包又得来一次,本地存着的时候没必要。真正需要的是「跨设备」这一步:上传、拷到另一台机器、寄给朋友,这时候包一次比什么都强。
顺便提一句,压缩和打包是两件事。tar 只是把文件串成一条,体积会变大;zip 会压缩,但压缩和解压各要一次 CPU 时间。字幕、nfo 这种文本压得很狠,可如果目录里混着视频,压不动的部分白搭时间。我一般只在不赶时间的时候加压缩。
列举目录反而不慢,别搞错方向
我原以为两万个文件会拖慢目录读取,测完发现想错了。
- 直接读目录项,两万个:16 到 19 毫秒
- 用命令行取完整文件信息,两万个:本地 2.0 秒,走共享 2.24 秒
- 换成 200 个目录、每目录 100 个文件,走共享列举:3.45 秒,比平铺还慢
读目录本身快得可以忽略,2 秒是花在给两万个文件逐个构造属性对象上。分桶这件事我也试了,没测出好处——多一层目录,遍历就多一层往返,文件总数没变,该花的还是得花。所以别为了「文件夹里文件太多」去做分桶,除非你的用途是人工翻找。
真正让人觉得卡的,是缩略图、预览、右键属性这类要逐个打开文件的操作,跟「目录里文件多」不是一回事。
我现在的做法
- 字幕不再一部剧散一堆:多语言塞进同一个字幕文件,或者直接封装进 mkv 当内嵌轨道,播放器里切换就行。一季剧从几十个碎文件变成一两个。
- 封面和 nfo 能合就合,不能合的就只留一份,不要每个版本目录复制一遍。
- 传给网盘、传给另一台机器之前,先打成包。一次请求和两万次请求,差的是两个数量级。
- 算容量别只看「大小」那一列。文件数乘以 233 字节,再加上每个文件按簇取整的损失,估出来的才是真占的地方。两万个 1KB 文件,按内容算 20MB,按实际得备 85MB。
- 备份和校验工具尽量别对碎片目录逐文件算哈希,那等于把上面那 9 毫秒再走一遍,还搭上 CPU。
你们 NAS 里文件数最多的是哪个目录?我的是剧集那边,至今没敢数第二遍。