VPN 共享上的 Visual Studio 为何卡顿:保存为什么变成了往返
通过共享目录编辑,会让保存、菜单和变更跟踪都变成往返请求。把工作副本移到本地,并把历史打包成一个对象,而不是逐个遍历。
从本地磁盘打开只需一秒的解决方案,从经由隧道访问的共享打开却要三十秒。隧道本身很少是原因。改变的是,原本属于本地调用的操作变成了同步往返,而编辑器在窗口出现之前就要执行好几次。下面的机制解释了每一步为什么昂贵,以及哪些可以去掉。
文件打开期间编辑器在做什么
带变更跟踪的编辑器会为每个打开的文件保留一个订阅。本地卷由文件系统直接提供这个订阅,不经过网络。跨共享时,同一功能可能退化成反复读取元数据,因为通知模型在 SMB 上的行为并不一样。关掉变更跟踪是合理的第一步实验,它在不碰隧道的前提下整整去掉了一类请求。
项目系统会带来自己的流量。加载解决方案要枚举目录、读取项目文件,还可能探测 SDK 或包,而只要路径在远端,每次探测都是一次往返。项目很多的解决方案会把次数成倍放大,因此打开时间的增长比文件大小所暗示的更快。
菜单出现之前就已超时的外壳查询
Windows 还贡献了第二个与 IDE 无关的卡顿来源。每次打开右键菜单,资源管理器及其外壳扩展都会重新解析之前用过的打印机、映射驱动器和快捷方式。当这些目标位于隧道无法到达的网络上时,每次解析都要阻塞到超时,菜单才画出来。这就是为什么文件本身打开正常,右键却会卡上几秒。
删掉失效的映射和打印队列,或者只保留你实际所在网络上存在的那些。同样的道理适用于任何在后台解析路径的东西,包括最近文件列表和固定文件夹。
net use
Get-Printer | Select-Object Name, PortName
Measure-Command { Get-ChildItem \files\proj | Out-Null }
为什么本地工作副本更划算
直接在共享上工作是编辑—构建—测试循环里最糟的配置,因为编辑器、编译器和版本控制客户端各自独立读取那些远端文件。每一个都要付出自己的往返,而一次构建要为每个文件付一次。通过仓库推送的本地副本,把大量细小的穿越换成一次有意的穿越。链路没有变好,穿越次数却塌了下来。
这是结构性改动,不是调参练习,这也正是它在隧道设置无效时仍然有效的原因。它同时还让故障模式变得清楚:如果用了本地副本构建仍然慢,那共享从来就不是问题。
把历史作为一个对象搬运
当仓库本身必须穿过隧道时,把历史作为一个对象发送,而不是逐个遍历。打包文件是一个文件,因此只有一次传输;而逐个走过成千上万个松散对象的检出,要付出成千上万次穿越。Mercurial 和 Git 都支持这种形式。
hg bundle --base 9f2c1a7e outgoing.hg
hg unbundle outgoing.hg
git bundle create repo.bundle main
git clone repo.bundle repo
基准版本是必须弄对的部分,因为它决定有多少历史要上路。全部打包能用但浪费链路,而从最后一个共同变更打包,只搬对方缺少的部分。
先测量三个操作,再怪带宽
把三件事分开测:打开一个小文件、列出一个目录、完成一次保存。如果三者都随文件数量同比例变慢,代价就是往返。如果只有一个卡住而其他正常,要怀疑是针对不可达资源的超时,而不是带宽。每次运行都记录机器在哪个网络上,因为笔记本重新连到另一个配置文件,就可能让答案在几次尝试之间改变。
然后对项目的本地副本重复同样三项。差值就是共享的代价,当有人提议换条更快的链路来解决时,要引用的就是这个数字。