簡體   English   中英

如何對異常緩慢的 git-diff 進行故障排除?

[英]How to troubleshoot an abnormally slow git-diff?

我最近克隆了一個遠程倉庫,其中一些git命令運行得非常慢。 例如,運行

git diff --quiet

...需要約 40 秒。 (就其價值而言,repo 是干凈的。我使用的是git版本 2.20.1。)

在試圖找出導致這種緩慢的原因時,我遇到了一些廢除它的程序,盡管我不知道為什么。

在這些過程中,我發現的最簡單/最快的一個是:(從一個新克隆的 repo 實例開始)在master創建一個分支,然后將其檢出。 在此之后,如果我再次檢查master ,現在git diff --quiet快速完成(低於 50 毫秒)。

下面是一個示例交互,顯示了各種操作1 的時間信息:

rm -rf ./"$REPONAME"      #  0.174 s
git clone "$URL"          # 54.118 s
cd ./"$REPONAME"          #  0.007 s

git diff --quiet          # 39.438 s

git branch VOODOO         #  0.032 s
git checkout VOODOO       # 31.247 s
git diff --quiet          #  0.014 s

git checkout master       #  0.034 s
git diff --quiet          #  0.012 s

正如我已經強調的那樣,這只是“修復”回購的幾種可能程序之一,它們對我來說都同樣神秘。 這只是我發現的最簡單/最快的一個。

上面的時序序列是非常可重復的(即,每次我完全按照所示運行特定序列時,我得到的時序大致相同)。

然而,它對看似微小的變化非常敏感。 例如,如果我替換git branch VOODOO; git checkout VOODOO git branch VOODOO; git checkout VOODOOgit checkout -b VOODOO ,隨后的時序配置文件發生了根本性的變化:

rm -rf ./"$REPONAME"      #  0.015 s
git clone "$URL"          # 45.312 s
cd ./"$REPONAME"          #  0.007 s

git diff --quiet          # 46.145 s

git checkout -b VOODOO    # 42.363 s
git diff --quiet          # 41.180 s

git checkout master       # 47.345 s
git diff --quiet          #  0.018 s

我想弄清楚發生了什么。 如何進一步解決問題?

是否有永久(“可提交”)的方式來“修復”回購? (“修復”我的意思是:擺脫git diff --quietgit checkout ...等的長時間延遲。)

(順便說一句, git gc不會修復回購,即使是暫時的;我試過了。)

我認為最終“修復”repo 的是git開始構建和緩存一些輔助數據結構,使其能夠有效地執行某些操作。 如果這個假設是正確的,那么我的問題可以改寫為:讓git構建這種輔助數據結構的最直接方法是什么?


編輯:可以闡明上述內容的另一條信息是該存儲庫包含一個特別大的 (1GB) 文件。 (這解釋了git clone步驟的緩慢。我不知道這是否與git diff --quiet等的緩慢有關,如果是,如何。)


1不用說,我將分支命名為VOODOO以反映我對正在發生的事情的無知。

首先檢查 Git 2.27 甚至即將推出的 2.28(2020 年第 3 季度)問題是否仍然存在

我會使用GIT_TRACE2_PERF來衡量任何性能。 (就像我在這里所做的那樣

使用 Git 2.28(2020 年第 3 季度),在具有太多統計信息不匹配路徑的工作樹中“ diff --quiet ”期間的內存使用量已大大減少。

它的補丁描述說明了一個用例,其中“ diff --quiet ”可能很慢:

請參閱Jeff King ( peff ) 的commit d2d7fbe (2020 年 6 月 1 peff )
(由Junio C gitster合並-- gitster -- in commit 0cd0afc ,2020 年 6 月 18 日)

diff : 從統計不匹配對中丟棄 blob 數據

報道者:Jan Christoph Uhde
簽字人:Jeff King

當對工作樹執行樹級差異時,我們可能會發現我們的索引統計信息是臟的,因此我們將文件對排入隊列以供稍后檢查。
如果實際內容沒有改變,我們稱之為stat-unmatch 統計信息已過時,但沒有實際差異。

通常diffcore_std()會通過diffcore_skip_stat_unmatch()檢測並刪除這些相同的文件對。

但是,當使用“ --quiet ”時,我們希望在看到任何更改時立即停止差異,因此我們立即在diff_change()檢查 stat- diff_change()

該檢查可能需要我們將文件內容實際加載到diff_filespecs對中。
如果我們發現這對組合不是統計上的不匹配,那么沒什么大不了的; 無論如何,我們可能會稍后加載內容以生成補丁、進行重命名檢測等,因此我們希望保留它。
但是如果它是一個 stat-unmatch,那么我們就沒有更多的用處了; 重點是我們要丟棄這對。 但是,我們從不釋放分配的diff_filespec數據。

在大多數情況下,保留這些數據不是問題。 我們不希望有很多 stat-unmatch 條目,而且由於我們正在使用--quiet ,無論如何我們一看到如此真實的變化就會退出。

但是,在極端情況下,它會產生很大的不同:

  1. 我們通常會 mmap() 對的工作樹的一半。
    而且由於操作系統可能會限制地圖的總數,我們可以在大型存儲庫中與此沖突。 例如:

     $ cd linux $ git ls-files | wc -l 67959 $ sysctl vm.max_map_count vm.max_map_count = 65530 $ git ls-files | xargs touch ;# everything is stat-dirty! $ git diff --quiet fatal: mmap failed: Cannot allocate memory

擁有如此多的 stat-dirty 文件應該是不尋常的,但是如果您剛剛運行了像“ sed -i ”或類似的腳本,則有可能。

在此補丁之后,以上代碼正確退出,代碼為 0。

  1. 即使您沒有達到 mmap 限制,索引對的一半也會從對象數據庫中提取到堆內存中。
    再次在linux.git的克隆中,運行:

     $ git ls-files | head -n 10000 | xargs touch $ git diff --quiet

在此補丁之前達到 145MB 堆的峰值,之后達到 94MB。

此補丁通過釋放我們在diff_filespec中的“ --quiet ”stat- diff_changes檢查期間獲取的任何diff_filespec數據來解決該問題。
以后沒有人會需要這些數據,因此保留它沒有意義。
有幾點需要注意:

  • 我們可以完全跳過對隊列的排隊,這在理論上可以節省一點工作。 但是沒有什么可節省的,因為無論如何我們都需要一個diff_filepair來提供給diff_filespec_check_stat_unmatch()
    由於我們緩存了stat-unmatch檢查的結果,稍后對diffcore_skip_stat_unmatch()調用將快速跳過它們。
    diffcore代碼還會在刪除它們時計算 stat- diffcore對的數量。 --quiet結合使用時,任何調用者都會關心這一點是值得懷疑的,但為了安全起見,我們必須重新實現這里的邏輯。 所以這真的不值得麻煩。

  • 我沒有編寫測試,因為我們總是會產生正確的輸出,除非我們遇到系統 mmap 限制,而系統 mmap 限制是機器人不可移植的,並且測試成本很高。 測量峰值堆會很有趣,但我們的性能套件還不能做到這一點。

  • 請注意,沒有“ --quiet ”的差異不會遇到同樣的問題。 diffcore_skip_stat_unmatch() ,我們檢測到stat-unmatch條目並立即刪除它們,因此我們不會攜帶它們的數據。

  • 如果您確實有那么多實際更改的文件,您仍然可以觸發mmap限制問題。 但這不太可能。 如果大小不匹配,則stat-unmatch檢查會避免加載文件內容,因此您需要對每個文件進行非常微不足道的更改。
    同樣,不精確的重命名檢測可能會同時加載許多文件的數據。 但是您不僅需要進行 64k 次更改,還需要進行大量的刪除和添加操作。 最有可能的候選者可能是中斷檢測,它將加載所有對的數據並將其保留在內容級別差異中。 但同樣,您首先需要 64k 實際更改的文件。

所以仍然有可能觸發這種情況,但似乎“我不小心把我的所有文件都弄臟了”是現實世界中最有可能的情況。


在 Git 2.30(2021 年第一季度)中,“ git diffman和其他共享相同機制以與工作樹文件進行比較的命令已被教導在可用時利用fsmonitor數據。

請參閱Nipunn Koorapati ( nipunn1313 ) 的commit 2bfa953commit 471b115commit ed5a245commit 89afd5fcommit 5851462commit dc69d47 (2020 年 10 月 20 日
請參閱Alex Vandiver ( alexmv ) 的commit c9052a8 (20 Oct 2020 )
(由Junio C gitster -- gitster --提交 bf69da5 中合並,2020 年 11 月 9 日)

t/perf : 為git diff添加fsmonitor perf 測試

簽字人:Nipunn Koorapati

parent-rev中補丁中git-diff fsmonitor優化的結果(使用400k文件repo進行測試)

正如你在這里看到的 - 運行fsmonitor git diff ( man )在這個補丁系列中明顯更好(我的工作量快了 80%)!

GIT_PERF_LARGE_REPO=~/src/server ./run v2.29.0-rc1 。 -- p7519-fsmonitor.sh

 Test v2.29.0-rc1 this tree ----------------------------------------------------------------------------------------------------------------- 7519.2: status (fsmonitor=.git/hooks/fsmonitor-watchman) 1.46(0.82+0.64) 1.47(0.83+0.62) +0.7% 7519.3: status -uno (fsmonitor=.git/hooks/fsmonitor-watchman) 0.16(0.12+0.04) 0.17(0.12+0.05) +6.3% 7519.4: status -uall (fsmonitor=.git/hooks/fsmonitor-watchman) 1.36(0.73+0.62) 1.37(0.76+0.60) +0.7% 7519.5: diff (fsmonitor=.git/hooks/fsmonitor-watchman) 0.85(0.22+0.63) 0.14(0.10+0.05) -83.5% 7519.6: diff -- 0_files (fsmonitor=.git/hooks/fsmonitor-watchman) 0.12(0.08+0.05) 0.13(0.11+0.02) +8.3% 7519.7: diff -- 10_files (fsmonitor=.git/hooks/fsmonitor-watchman) 0.12(0.08+0.04) 0.13(0.09+0.04) +8.3% 7519.8: diff -- 100_files (fsmonitor=.git/hooks/fsmonitor-watchman) 0.12(0.07+0.05) 0.13(0.07+0.06) +8.3% 7519.9: diff -- 1000_files (fsmonitor=.git/hooks/fsmonitor-watchman) 0.12(0.09+0.04) 0.13(0.08+0.05) +8.3% 7519.10: diff -- 10000_files (fsmonitor=.git/hooks/fsmonitor-watchman) 0.14(0.09+0.05) 0.13(0.10+0.03) -7.1% 7519.12: status (fsmonitor=) 1.67(0.93+1.49) 1.67(0.99+1.42) +0.0% 7519.13: status -uno (fsmonitor=) 0.37(0.30+0.82) 0.37(0.33+0.79) +0.0% 7519.14: status -uall (fsmonitor=) 1.58(0.97+1.35) 1.57(0.86+1.45) -0.6% 7519.15: diff (fsmonitor=) 0.34(0.28+0.83) 0.34(0.27+0.83) +0.0% 7519.16: diff -- 0_files (fsmonitor=) 0.09(0.06+0.04) 0.09(0.08+0.02) +0.0% 7519.17: diff -- 10_files (fsmonitor=) 0.09(0.07+0.03) 0.09(0.06+0.05) +0.0% 7519.18: diff -- 100_files (fsmonitor=) 0.09(0.06+0.04) 0.09(0.06+0.04) +0.0% 7519.19: diff -- 1000_files (fsmonitor=) 0.09(0.06+0.04) 0.09(0.05+0.05) +0.0% 7519.20: diff -- 10000_files (fsmonitor=) 0.10(0.08+0.04) 0.10(0.06+0.05) +0.0%

我還為帶有路徑規范的微小git diff ( man )工作負載添加了一個基准。 我看到使用和不使用fsmonitor增加了大約 0.02 秒的開銷。

通過查看這些結果,我懷疑在git diff ( man )期間已經發生了refresh_fsmonitor - 與此補丁系列的優化無關。
通過打破refresh_fsmonitor證實了這一懷疑。

 (gdb) bt [simplified]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM