Git difftool 在 Cygwin/MinGW 中慢得离谱

Git difftool ridiculously slow in Cygwin/MinGW

我注意到 git difftool 非常慢。每次差异调用之间出现大约 1..2 秒的延迟。

为了对其进行基准测试,我编写了一个自定义 difftool 命令:

#!/bin/sh
echo [=12=]  

并配置 Git 在我的 ~/.gitconfig

中使用此工具
[diff]
    tool = mydiff
[difftool "mydiff"]
    prompt = false
    cmd = "~/mydiff \"$LOCAL\" \"$REMOTE\""

我在 Git 来源上测试了它:

$ git clone https://github.com/git/git.git
$ cd git
$ git rev-parse HEAD
1bc8feaa7cc752fe3b902ccf83ae9332e40921db
$ git diff head~10 --stat --name-only | wc -l
23

当我用 259b5e6d33 计时 git difftool 时,结果慢得离谱:

$ time git difftool 259b5
mydiff /dev/null Documentation/RelNotes/2.6.3.txt
...
mydiff /tmp/mY2T6l_upload-pack.c upload-pack.c

real    0m10.381s
user    0m1.997s
sys     0m6.667s

通过尝试使用更简单的脚本,它运行得更快:

$ time git diff --name-only --stat 259b5 | xargs -n1 -I{} sh -c 'git show 259b5:{} > {}.tmp && ~/mydiff {} {}.tmp'
mydiff Documentation/RelNotes/2.6.3.txt Documentation/RelNotes/2.6.3.txt.tmp
mydiff upload-pack.c upload-pack.c.tmp

real    0m1.149s
user    0m0.472s
sys     0m0.821s

我错过了什么?

这是我得到的结果

| Cygwin | Debian | Ubuntu | Method   |
| ------ | ------ | ------ | -------- |
| 10.381 |  2.620 | 0.580  | difftool |
|  1.149 |  0.567 | 0.210  | custom   |

对于 Cygwin 结果,我测得 git-difftool 花费了 2.8 秒,git-difftool--helper 花费了 7.5 秒。后者有 98 行长。我不明白为什么这么慢。

使用发现的一些技术 on the msysgit GitHub,我已经缩小了范围。

对于 diff 中的每个文件,git-difftool--helper 重新运行以下内部命令:

12:44:46.941239 git.c:351               trace: built-in: git 'config' 'diff.tool'
12:44:47.359239 git.c:351               trace: built-in: git 'config' 'difftool.bc.cmd'
12:44:47.933239 git.c:351               trace: built-in: git 'config' '--bool' 'mergetool.prompt'
12:44:48.797239 git.c:351               trace: built-in: git 'config' '--bool' 'difftool.prompt'
12:44:49.696239 git.c:351               trace: built-in: git 'config' 'difftool.bc.cmd'
12:44:50.135239 git.c:351               trace: built-in: git 'config' 'difftool.bc.path'
12:44:50.422239 git.c:351               trace: built-in: git 'config' 'mergetool.bc.path'
12:44:51.060239 git.c:351               trace: built-in: git 'config' 'difftool.bc.cmd'
12:44:51.452239 git.c:351               trace: built-in: git 'config' 'difftool.bc.cmd'

请注意,在这种特殊情况下,执行这些命令大约需要 4.5 秒。这是我整个日志中非常一致的模式。

还要注意其中一些是重复的 - git config difftool.bc.cmd 被调用了 4 次!

现在,可能的补救措施:

  • 我将这些命令的执行时间减半,方法是将所有与差异相关的部分移至我的 .gitconfig 文件。严重地。它仍然很明显,但现在大约是 2 秒而不是 4.5。
  • 确保 Program Files 下的 Git 文件夹和您的用户配置文件(.gitconfig 所在的位置)都被排除在实时病毒扫描之外。
  • 从根本上说,Git 需要更高效地解析和获取配置值。理想情况下,它会缓存这些而不是在循环中每次都从配置中重新请求(和重新解析...)。甚至可能为整个命令执行缓存。

git difftool Git 2.13(2017 年第 2 季度)
应该稍微快一些 参见 commit d12a8cf (14 Apr 2017) by Jeff Hostetler (jeffhostetler)
(由 Junio C Hamano -- gitster -- in commit 8868ba1 合并,2017 年 4 月 24 日)

unpack-trees: avoid duplicate ODB lookups during checkout

(ODB:对象数据库)

Teach traverse_trees_recursive() to not do redundant ODB lookups when both directories refer to the same OID.

In operations such as read-tree and checkout, there will likely be many peer directories that have the same OID when the differences between the commits are relatively small.
In these cases we can avoid hitting the ODB multiple times for the same OID.

This patch handles n=2 and n=3 cases and simply copies the data rather than repeating the fill_tree_descriptor().

================

On the Windows repo (500K trees, 3.1M files, 450MB index), this reduced the overall time by 0.75 seconds when cycling between 2 commits with a single file difference.

(avg) before: 22.699
(avg) after:  21.955
===============

经过一些调查,我有证据表明性能不佳与来自不同域的用户所拥有的文件有关。具体来说,我得出了以下结论:

  • 我在一个拥有多个域和数千名用户的企业环境中工作。
  • 由于组织变化,每个用户可能仅在过渡阶段被保留在两个域中,即他或她的主域和第二域。通过 Windows GUI 更改对象所有权时,每个用户都会出现两次,并且必须转到扩展用户选择以识别分配给特定域的用户。
  • 启用 acl 的
  • cygwin 将 "other domain" 文件用户显示为“+”。主域自身就是“<用户名>”。在两种情况下,没有 acl 的 Cygwin 仅显示“”。这可能相当令人困惑,因为 cygwin 识别的文件权限和所有权将指示写入权限,而用户实际上没有。
  • 属于 "other domain" 自己的文件可由我 "this domain" 自己写入,因此域分配在很大程度上是透明的。
  • 来自我们版本控制系统的大型源代码树(也反映在 git 存储库中)具有 "other domain self" 拥有的数千个文件。这似乎导致文件操作缓慢。将所有权更改为 "primary domain self" 解决了 git 和其他文件访问的速度问题。

我必须假设为其他域中的用户获取文件权限很慢,并且由于某种原因没有缓存(它始终是同一用户)。

下面文章的其余部分是我最初发布的内容。我让它站起来。


对我来说(在一家拥有多个地理分布 Windows 域的大公司工作),罪魁祸首是默认的 cygwin uses Windows acl。考虑针对域中所有已知用户的此请求:

$ time (mkpasswd -D | wc -l)
45183

real    27m55,340s
user    0m1,637s
sys     0m0,123s

修复 (1)(2) 是使用 noacl 安装 NTFS 文件系统的简单问题,即我的 /etc/fstab 包含行

none / cygdrive binary,posix=0,user,noacl 0 0

(同时消除烦人的cygdrive前缀)。

我忍不住想象 cygwin/msys(除了 Windows git 安装默认安装 noacl,可能是这个原因)执行域服务器查询它接触的每个文件并且不缓存结果。

该更改是在 2015 年左右的某个时候在 cygwin 2.4 或 2.5 中引入的。来自 release notes for 2.4:

To accommodate standard Windows ACLs, the POSIX permissions of the owner and all other users in the ACL are computed using the Windows AuthZ API. This may slow down the computation of POSIX permissions noticably in some circumstances [...] (emphasis by me).

noacl 选项将启动 BeyondCompare(或回显字符串,就此而言)的时间从 25 秒减少到 1 秒。完全无法理解为什么在同一个文件上使用简单的 git diff即使使用 acl 也非常快,因为我会天真地假设所需的信息以及所需的 FS 操作是相同的。

我现在将查看 cygserver,它可能会通过缓存改进一些东西。

更新: 不幸的是,cygserver 没有改善这种情况。


(1) git 的修复。 mkpasswd 不受影响。

(2) 对于 git(以及我们也通过 cygwin 访问的 ClearCase 视图),我没有理解和测试对文件权限和所有权的影响。我的直觉是,人们希望尽可能地忠实于 Windows 语义(这意味着 noacl 可能 运行 出现问题)。

(3) cygwin documentation 讨论了不缓存查询结果的场景。一个由一系列 cygwin 进程组成,这些进程不是从共同的 cygwin 祖先(如 bash)产生的,而是从 windows 程序如 cmd 产生的。我必须假设 Windows 为本机程序提供了缓存机制,否则 Windows 系统将无法在该企业环境中使用。出于某种原因,cygwin 不使用它。