Files
klink/PERFORMANCE_IMPROVEMENTS.md
T
gary db6d64e9c9
CI / test (push) Successful in 1m7s
CI / cross-build (amd64, darwin) (push) Successful in 38s
CI / cross-build (amd64, linux) (push) Successful in 33s
CI / cross-build (amd64, windows) (push) Successful in 38s
CI / cross-build (arm64, darwin) (push) Successful in 39s
CI / cross-build (arm64, linux) (push) Successful in 39s
CI / cross-build (arm64, windows) (push) Successful in 38s
perf: optimize SFTP sessions and configuration imports
2026-09-13 18:19:56 +08:00

159 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 性能与算法优化方向
本文记录当前审查得出的性能和算法优化方向,供评审和任务拆分使用。优先级按预期用户收益排序,不按实现工作量排序。
## P0:恢复 SFTP 并发传输路径
### 现状
`internal/sftp/transport.go` 已启用 SFTP 并发读写,但
`internal/sftp/service.go` 为进度和取消逻辑包裹了 `io.Copy` 的两端。包装类型隐藏了
`pkg/sftp.File` 的 `WriteTo` 和 `ReadFrom` 方法,使依赖库无法走并发传输路径。
在高延迟连接中,当前约 32 KiB 的串行分块可能接近每个往返只传输一个分块。以 80 ms RTT 估算,忽略协议开销时上限约为 0.4 MiB/s。
### 优化方向
- 下载时通过远端 SFTP 文件的 `WriteTo` 路径传输,同时保留带进度统计的本地 writer。
- 上传时通过远端 SFTP 文件的 `ReadFrom` 或 `ReadFromWithConcurrency` 路径传输,并提供支持 context、可报告大小的本地 reader。
- 保留现有的取消机制:等待已有宽限期后关闭 SFTP 会话。
- 保持临时文件原子完成和断点续传语义。
### 实现约束
- 并发远端写入在失败后可能留下超出首个失败偏移的数据。失败时必须截断或丢弃远端临时文件,不能将其发布为最终目标。
- 保持现有的覆盖确认和 partial 文件校验保证。
- 仅在真实测量表明默认值不适合常见服务器时,再将并发度设为可配置项。
### 验收标准
- 使用注入延迟的基准测试,证明大于两个 SFTP 包的文件吞吐有明显提升。
- 取消、覆盖、中断上传、中断下载和续传测试继续通过。
- 重复取消时不存在 goroutine 或文件描述符泄漏。
## P1:消除目录传输的重复遍历
### 现状
目录传输先计算文件数和总字节数,再次遍历后实际传输。远端下载会执行两次递归 `ReadDir`,并且在已经读取到目录项后,仍为每个文件单独发起一次 `Stat`。
### 优化方向
- 在首次遍历远端目录时构建传输清单,包含远端路径、本地目标路径和复制所需的 `FileInfo`。
- 用同一份清单计算总量和执行传输;将已知文件元数据传给 `downloadFile`,避免额外 `Stat`。
- 对超大清单设置合理内存上限;超过上限时退化为不显示总量的整体进度,但保留单文件进度。
- 在远端下载路径完成后,再考虑将本地上传也改成清单模式。本地双遍历的成本低于额外远端往返。
### 验收标准
- 下载目录树时,每个远端目录只在准备阶段读取一次,不再出现可避免的逐文件 `Stat`。
- 文件数、总字节、符号链接拒绝和错误信息保持现有行为。
- 使用带请求计数的远端文件系统测试,断言请求次数。
## P1:复用交互式 SFTP 会话
### 现状
每次未命中缓存的远端列表、变更、上传和下载都会启动新的 `ssh -s ... sftp` 子进程,并在操作结束时关闭。目录列表缓存能帮助重复访问,但首次连续进入目录树仍会反复认证和握手。
### 优化方向
- 引入有上限的会话管理器,按节点 ID 和解析后的地址索引;每个活跃操作租用一个 SFTP 客户端。
- 在短暂空闲后关闭会话;遇到传输或认证错误时使会话失效。
- 备选方案是评估 OpenSSH `ControlMaster`/`ControlPersist` 复用。它可减少应用内连接池逻辑,但需要谨慎处理控制 socket 生命周期和跨平台差异。
### 验收标准
- 同一节点连续浏览未缓存目录时,在安全情况下复用一次已认证连接。
- 失效或过期的会话会在下一次操作时透明替换。
- 应用退出和节点配置变更时,受影响的会话会关闭或失效。
## P1:减少 TUI 空闲工作和搜索分配
### 现状
marquee 定时器即使没有任何文本需要滚动,也会每 250 ms 运行一次。一次渲染可能多次过滤节点;过滤会为每个节点构造小写搜索字符串并分配新的结果切片。
### 优化方向
- 仅在当前可见文本实际溢出时启动 marquee 定时器,离开该状态后停止。
- 以查询值和单调递增的配置版本缓存过滤后的节点 ID 或索引。
- 为节点保存规范化搜索字段,或与过滤结果一起缓存;编辑、导入、重载和删除后使缓存失效。
- 过滤时返回指向 `m.cfg.Nodes` 的引用或索引,而不是复制 `Node` 值。
### 验收标准
- 空闲界面在没有动画时不再调度 250 ms 重绘。
- 相同查询的重复渲染不再扫描全部节点。
- 为 100、1,000、10,000 节点增加基准测试,并保持当前光标和搜索行为。
## P2:批量观察隧道状态
### 现状
TUI 每秒刷新一次隧道状态。每条活跃 Unix 隧道会进行状态文件 I/O、存活检查,并启动独立 `ps` 子进程验证 worker 所有权;所有 profile 串行处理。
### 优化方向
- 高频轮询活跃或状态转换中的隧道;缓存终态,降低其刷新频率,或仅在用户操作后刷新。
- 增加批量状态 API,集中加载状态,并在平台允许时批量验证进程所有权。
- 每次发信号前仍保留 PID/token 所有权验证。性能优化不得削弱 PID 复用安全边界。
### 验收标准
- 大量已停止隧道不会各自触发周期性的进程检查。
- 停止/重启安全测试,包括 PID 复用保护,继续通过。
## P3:为导入和批量配置操作建立索引
### 现状
`SyncKTUI` 和 `Merge` 会反复扫描已有节点和隧道来检查 ID、名称和地址匹配。大型导入时算法复杂度为二次。
### 优化方向
- 导入前建立节点 ID、规范化节点名称、受管理节点身份、隧道 ID 和规范化隧道名称的 map。
- 每次接受新记录后同步更新 map,确保同一次导入内部的冲突也能被检测。
- 保持现有的冲突输出顺序和 ktui 所有权语义。
### 验收标准
- 除必要的输出排序外,大型导入的复杂度为已有记录数加新增记录数的线性复杂度。
- 现有导入冲突和幂等性测试不变,或补充为更明确的断言。
## P3:节流传输进度发布
### 现状
每个复制分块都会发布进度。TUI 侧每次更新都会锁定任务并读取时钟,但速度显示本身已使用 200 ms 采样窗口。
### 优化方向
- 在传输路径中原子累加已传输字节数。
- 每 100-200 ms 发布一次 UI 进度;文件完成和传输完成时额外发布。
- 保持进度单调递增,并确保完成消息前发出最终进度。
### 验收标准
- 高速本地或局域网传输不再每个分块都发生 UI 加锁和时钟读取。
- 进度条、最终字节数、取消和任务队列行为不变。
## 测量与交付顺序
1. 修改 P0 前先增加可复现基准:模拟 5 ms、40 ms、100 ms RTT 的单文件上传/下载、小文件目录树、续传和 TUI 过滤。
2. 先实现并测量 P0。它预期吞吐收益最大,且依赖库已提供明确的优化路径。
3. 接着实现远端传输清单,再评估会话复用。
4. 在修改渲染调度器前,对空闲和大规模配置的 TUI 进行 profile。
5. 增加普通 push/pull-request CI,执行 `go test ./...`、`go vet ./...` 和重点 race 测试。现有工作流只在发布 tag 时验证。
## 实施状态(2026-07-24)
已完成 P0 和 P1:
- P0:非续传上传重新使用 `ReaderFrom`,下载重新使用 `WriterTo`,从而让 `pkg/sftp` 的并发读写选项真正参与数据复制;续传仍使用顺序写入,避免并发写入失败时留下稀疏文件。
- P1(目录传输):上传和下载均先建立一次传输清单;下载不再对每个远端文件额外 `Stat`,也不再为统计与实际传输重复遍历目录。
- P1(会话):按节点和地址缓存空闲 SFTP 会话,支持空闲过期、容量限制、节点失效和应用退出时关闭;配置重载时会使会话失效。
- P1(TUI):缓存节点搜索结果;仅在页面存在溢出文本时调度跑马灯刷新,空闲页面不再持续产生 250 ms 更新。
新增覆盖上述路径的单元测试和基准。验证已执行:`go test ./...`、`go vet ./...`、`go test -race ./...`、`go build ./cmd/klink`。