扫描速度与磁盘负载
SpaceKnife 尽量按磁盘允许的最快速度扫描,同时给正在用的程序留出余量。本页讲两种扫描方式、让扫描保持克制的磁盘负载上限,以及告诉你扫描在等什么的那些数字。
| 想要 | 做法 |
|---|---|
| 最快扫完一块 Windows 磁盘 | 扫描整个盘(不是其中的文件夹),扫描方式用「自动」或「快速 MFT」,并同意管理员授权:MFT 快速扫描整盘几秒完成 |
| 最快的硬扫描 | 开始前在「磁盘负载上限」里选「极速」,或扫描中在「扫描详情」里改 |
| 扫描时不影响工作 | 「安静 35%」,或者只在磁盘空闲时读取的「空闲」 |
| 夜里或在脚本里扫描 | 用命令行加 --disk-limit idle(见命令行) |
| 找出扫描慢在哪里 | 看日志里的耗时分解,或用命令行加 --profile |
两种扫描方式
Section titled “两种扫描方式”| MFT 快速扫描 | 硬扫描 | |
|---|---|---|
| 适用 | Windows,整个本地 NTFS 卷 | 任何位置 |
| 原理 | 通过只读扫描助手,一次顺序读取 NTFS 主文件表 | 逐目录遍历,多个目录并行 |
| 速度 | 整盘几秒完成 | 耗时随文件数量增长 |
| 磁盘负载上限 | 放慢读取节奏;没有可调的并发 | 限制同时读取的目录批数 |
| 暂停 | 不可用:几秒就完成 | 可以 |
| 权限 | 扫描助手请求管理员权限,主程序从不提权 | 你自己的权限 |
在「设置 › 扫描与性能 › 扫描方式 › 默认扫描方式」里选择:
- 「自动」(默认):整个 NTFS 卷用 MFT 快速扫描,其他位置逐目录硬扫描。
- 「快速 MFT」:总是先尝试 MFT 快速扫描;用不了时改用硬扫描,并说明原因。
- 「硬扫描」:总是逐目录遍历。
启动页的「扫描选项」里有同样的选择,每个卷还会标出将用什么方式扫描:「MFT 快速扫描 · 需要管理员」「并行硬扫描」「并行硬扫描 · USB HDD 自动降速」「网络扫描 · 较慢」或「不推荐 · 很慢」。
只有管理员权限才能读取 MFT,所以 SpaceKnife 为此启动一个小的只读「扫描助手」,只有它请求管理员权限(UAC 提示)。助手只做固定的几种只读操作:为快速扫描读取 MFT,为实时监控读取 USN 日志。
- 拒绝授权时,扫描继续以硬扫描进行,「扫描详情」里会说明。
- 「需要时请求管理员权限」(同一设置页,默认开):关掉后,整个 NTFS 卷直接用硬扫描,不再弹出提示(SpaceKnife 本身已有管理员权限时除外)。
- 整个会话共用一个助手。助手空闲 10 分钟后退出,之后的快速扫描会再请求一次授权(SpaceKnife 本身以管理员身份运行时不会)。「设置 › 高级 › 扫描助手与缓存」显示它是否在运行,并有「停止扫描助手」。
- 硬扫描整个 NTFS 卷时,「扫描详情 › 扫描方式」里有「以管理员身份启用 MFT 快速扫描」。助手启动期间硬扫描照常进行,MFT 结果就绪后替换它。
MFT 快速扫描不显示百分比,而是显示阶段:「正在等待管理员授权…」「正在读取 MFT 主文件表…」「正在接收扫描结果…」「正在建立目录树…」。读完后一次建好目录树。
硬扫描是怎么进行的
Section titled “硬扫描是怎么进行的”硬扫描按批列目录:每次调用最多列 256 个目录,最多 50 毫秒就带着已列出的结果返回。这些批次在一组列目录线程上运行;主线程把每一批合并进目录树,所以扫描过程中画面会不断长出来。
- 列目录线程:每个 CPU 一个,在 2–16 之间。macOS 上是 CPU 数的两倍,在 4–16 之间:APFS 列目录时经常在内核里等待,多出来的线程能让 CPU 保持忙碌。
SPACEKNIFE_SCAN_THREADS可以改这个数(1–64)。 - 原生遍历器:列目录由 Rust 写的原生扩展完成,每个安装包都带。没有它时(没装 Rust 的源码环境),改由工作进程里的 Python 遍历器完成,慢好几倍。
- 系统调用:Windows 上一次
NtQueryDirectoryFile就拿到名字、大小、占用、时间、属性和文件 ID;Linux 上是openat、getdents64和fstatat;macOS 上文件用getattrlistbulk,文件夹用fstatat。 - 同时进行多少批由磁盘负载上限和磁盘类型决定(见下文)。
进度与剩余时间
Section titled “进度与剩余时间”- 这个位置以前扫描过时,最近一次完整的快照给出预计的项目数,进度按项目数计算。扫描文件夹时,进度条还会显示「(上次 N 项)」。
- 否则,扫描整个卷时按已找到的字节数与卷的已用空间比较,最多算到 95%。已用空间并不是精确的总数(硬链接、克隆、快照和系统区域都会让它偏差),所以超过这一点后百分比显示「…」,而不是一直停在 99%。
- 剩余时间在最初 3 秒之后出现,按最近几秒的速度估算,而不是从开始到现在的平均速度。
启动页会显示每个卷的介质。怕并行读取的磁盘同时进行的批数更少,不受档位影响(手动设置并发时除外):
| 介质 | 同时最多几批 |
|---|---|
| 机械硬盘、USB HDD、U 盘、存储卡 | 2 |
| USB SSD | 4 |
| 网络 | 8 |
| NVMe SSD、SATA SSD、SSD、虚拟磁盘、未知介质 | 全部列目录线程 |
机械硬盘同时读很多目录时时间都花在寻道上,一次两个反而更快。
磁盘负载上限
Section titled “磁盘负载上限”上限限制扫描最多能占用多少磁盘读取能力:
| 档位 | 作用 |
|---|---|
| 「极速」 | 不限制磁盘读取 |
| 「均衡 70%」 | 扫描最多占用 70% 的磁盘读取,给前台程序留出余量 |
| 「安静 35%」 | 扫描最多占用 35% 的磁盘读取,几乎感觉不到 |
| 「空闲」 | 只在磁盘空闲时读取,并使用系统低 I/O 优先级 |
| 「自定义」 | 按设置里的「自定义上限」 |
在哪里设置:
- 「启动页 › 扫描选项 › 磁盘负载上限」:对即将开始的扫描生效。
- 「扫描详情」(扫描时的侧栏)›「磁盘负载上限」:随时可改,立即生效;方向键可以在档位之间切换。
- 「设置 › 扫描与性能 › 磁盘负载上限与并发」:「手动扫描默认」(均衡 70%)是启动页的初始档位;「自定义上限」(70%,5–100,步长 5)是「自定义」档的百分比。
- 命令行:
--disk-limit。
状态栏显示档位和当前负载,例如「均衡 ≤70%」;扫描中点击它会打开「扫描详情」。
负载怎么测量
Section titled “负载怎么测量”SpaceKnife 从启动起就采样每一块物理磁盘,不需要管理员权限:Windows 用性能计数器,Linux 读 /sys/block/<磁盘>/stat,macOS 读 IOKit 的块存储统计(和 iostat 同源)。
- 负载取两个数中较小的一个:磁盘有请求在处理的时间占比;平均排队的请求数除以磁盘能并行处理的请求数(机械硬盘 1、SATA SSD 8、NVMe SSD 32,扫描中再按实测修正)。
- 活动时间就是前一个数,也就是任务管理器的口径。NVMe 固态盘上几个并发读就能把活动时间推到 100%,而盘远没有饱和,所以上限按负载计算,活动时间只作参考。
- SpaceKnife 自身的占用 = 负载减去其他程序造成的部分。其他程序的部分在 SpaceKnife 没有请求在途时测量:扫描开始前、暂停时,以及每隔 8–32 秒一次的短暂探测(暂停派发片刻);让路期间探测得更频繁。
- 控制:目标略低于上限。每次采样都会调整:低于上限时每半秒最多多放行一批(离上限很远时更快,例如刚开始时);超过上限时最多减半。最低保持一批的 5%,所以扫描总会有进展。
- 每块物理磁盘一个限速器:同一块盘上的扫描共用它。
- macOS 不提供空闲时间,忙碌占比按队列长度估算。
- 测不到负载的磁盘(例如网络盘)在「扫描详情」里显示「无法测量此磁盘」,这时上限按最大批数的固定比例执行。
给其他程序让路
Section titled “给其他程序让路”「整盘繁忙时让路」(默认开)作用于均衡、安静和自定义档:其他程序单独就让整块磁盘超过上限时,扫描降到最低进度,直到它们回落到上限的 85% 以下。这时「扫描详情」显示「其他程序正在大量读写这块磁盘,扫描已让路(最低进度)」。
「空闲」档总会让路:其他程序占用磁盘超过 10% 就让。「极速」从不让路。
低 I/O 优先级
Section titled “低 I/O 优先级”「空闲时使用系统低 I/O 优先级」(默认开)让「空闲」档以操作系统的低 I/O 优先级读取,由系统自己优先满足其他程序:
- Windows:最低的 I/O 优先级。
- Linux:idle I/O 类。只有支持优先级的 I/O 调度器(BFQ、较新的 mq-deadline)会理会它;NVMe 上常见的
none调度器会忽略。 - macOS:限流的 I/O 策略。
「扫描详情」显示「并发 N · 进行中 N」。打开「自动调节」时由上限决定;关掉后可以用 − 和 + 自己设定。「设置 › 扫描与性能 › 最大并发」为新扫描选择「自动」或「手动」,「手动并发数」设定数值(默认 4)。手动的数值不会超过列目录线程数;要更多,用 SPACEKNIFE_SCAN_THREADS 增加线程。
暂停、停止与继续
Section titled “暂停、停止与继续”- 「暂停」停止派发新的批次:SpaceKnife 的读盘在一秒内降到零。「继续」从原处接着扫。
- 「停止」保留已扫到的结果(「已停止(结果保留)」)。停止的硬扫描之后可以接着扫:状态栏的「继续扫描」(提示里有还没读取的文件夹数),或命令面板的「继续已停止的扫描」。
- 命令面板里的「重新扫描当前视图」只重扫放大进入的文件夹;「全盘重新扫描」把所有扫描根重新扫一遍。0.3.2 起直接按 F5、Shift+F5(见快捷键);在多个扫描位置的最顶层,「重新扫描当前视图」把所有位置都重读一遍。开着实时监控时目录树本来就是最新的,很少需要整盘重扫(见实时监控)。
读懂这些数字
Section titled “读懂这些数字”| 显示 | 含义 |
|---|---|
| 「当前 N%」 | SpaceKnife 最近 1 秒在这块磁盘上造成的读取占用(受上限控制) |
| 「整盘 N%」 | 整块磁盘的负载,包括其他程序 |
| 「活动时间 N%」 | 任务管理器的口径,只作参考(见上文) |
| 「磁盘读取占用 · 近 60 秒」 | 面积:整盘;实线:SpaceKnife;虚线:上限;活动时间也画在图上,只作参考 |
| 「整盘读取 … · 平均延迟 N 毫秒」 | 最近一次采样里整块磁盘的读取速度和每次读取的平均耗时,包括其他程序 |
| 「并发 N · 进行中 N」 | 允许同时进行的批数,和正在进行的批数 |
日志里的耗时分解
Section titled “日志里的耗时分解”每次硬扫描结束后,日志文件里会有两行这样的记录(数字只是示例):
scan done: 1,234,567 items, 1,103,402 files, 210.4 GiB in 41.20s (29,965 items/s), 12 errorsscan profile: 1,234,567 items in 41.2s (29,965/s); listing threads busy 92% of 16; main thread folding 38%; held by the disk-load limit 0%; 4,823 slices; getattrlistbulk on日志文件是日志文件夹里的 spaceknife.log(「设置 › 高级 › 文件与诊断 › 日志文件夹」,另见故障排除)。第二行说明扫描在等什么:
| 片段 | 含义 | 怎么办 |
|---|---|---|
listing threads busy 92% of 16 |
16 个列目录线程把 92% 的时间花在系统调用上 | 接近 100%:瓶颈在磁盘或文件系统。SSD 还有余量时,多开线程(SPACEKNIFE_SCAN_THREADS)可能有用;磁盘本身忙或慢时没有办法 |
main thread folding 38% |
主线程把结果合并进目录树所占的时间 | 接近 100%:瓶颈在合并,加线程没用 |
held by the disk-load limit 0% |
因为上限拦住下一批、没有任何目录在列的时间 | 很高:上限在起作用。想要速度就提高档位或选「极速」 |
4,823 slices |
列了多少批 | — |
getattrlistbulk on |
只在 macOS 上:批量列目录是 on、off: 原因 还是 untested |
— |
忙碌和合并都很低时,说明列目录线程在等活干:目录树又深又窄,或者扫描正在给其他程序让路。命令行的 --profile 输出同样的数字。
内存与超大磁盘
Section titled “内存与超大磁盘”扫描数据按列紧凑存放:名字长度一般时,每项(一个文件或文件夹)约 134 字节。
| 项数 | 内存 |
|---|---|
| 100 万 | 约 0.13 GB |
| 1000 万 | 约 1.3 GB |
| 5000 万 | 约 6.7 GB |
| 1 亿 | 约 13.4 GB |
- 「扫描数据内存上限」(「设置 › 高级 › 内存」,默认 25%,5–90):打开的扫描超过物理内存的这个比例后,新的节点表存进缓存文件夹里的磁盘映射文件。它们不计入进程的私有内存;常用的部分由系统留在内存里,其余的丢掉,需要时再从磁盘读回(遍历整个扫描时可能多花零点几秒)。这些文件会自动删除,进程崩溃也不会留下。磁盘没有空间时,SpaceKnife 继续留在内存里。
- 最多 8 个扫描留在内存里。再开一个时,最早的、已经结束且没有打开视图的扫描会被移出。
- 关闭扫描就释放它的数据。进程可能留着一部分释放出来的内存给下一次扫描复用,这是正常的。
- MFT 快速扫描:助手的峰值约等于结果本身的大小(837 万项约 1.6 GB);随后建目录树时短暂需要每项约 330 字节,建好后约每项 100 字节。
- 内存看门狗:后端每 15 秒比较一次自己的私有内存和打开的扫描应占的量。明显超出时(扫描数据的两倍加 1.5 GB,再加最近一小时内关掉的最大那次扫描),先把释放出来的内存还给系统,仍然超出才在日志面板提示「后端内存占用异常:…」,并把明细写进日志文件。见故障排除。
扫描超大的磁盘:Windows 上用 MFT 快速扫描;其他平台扫描你关心的文件夹,而不是整个盘;保持「扫描后自动保存快照」开启,下次扫描同一位置时进度按项目数计算;不再需要的标签页随手关掉。
网络盘和其他慢的位置
Section titled “网络盘和其他慢的位置”- 网络盘标为「网络扫描 · 较慢」。测不到它的负载,同时最多列 8 批。
- 从 WSL 访问的 Windows 盘(
/mnt/c等)标为「不推荐 · 很慢」:它们经过 9P 协议。请改用 Windows 版的 SpaceKnife 扫描。 - 没有连接的网络卷无法扫描(「网络卷未连接」)。
- 「跨越挂载点」(「设置 › 扫描与性能 › 扫描范围」,默认关):关闭时扫描只统计开始时所在的卷,不进入挂载在下面的其他卷。
- 完全磁盘访问权限:没有它时,macOS 会为桌面、文稿、下载、云盘、照片图库和其他应用的数据逐个弹窗询问,邮件、信息等位置则完全读不到(计入未知空间)。见故障排除。
- 批量列目录:文件属性用
getattrlistbulk批量获取(文件夹、挂载点和 firmlink 仍用fstatat)。每个进程列出的第一批会和fstatat的结果对照,有任何差异就永久退回readdir加fstatat。SPACEKNIFE_NO_BULK=1可以关掉它来对比速度。 - 扫描
/时跳过/System/Volumes/Data:它的文件已经通过 firmlink 出现在树里。 - 云端文件:只在 iCloud 云盘或其他云服务里的文件按占用 0 计,这样的文件夹不进入,也不会触发下载。