# 性能与算法优化方向 本文记录当前审查得出的性能和算法优化方向,供评审和任务拆分使用。优先级按预期用户收益排序,不按实现工作量排序。 ## 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`。