Как улучшить производительность git log?
Я пытаюсь извлечь журналы Git из нескольких репозиториев, как это:
git log --pretty=format:%H\t%ae\t%an\t%at\t%s --numstat
Для больших репозиториев (таких как рельсы / рельсы) на создание журнала уходит 35 с лишним секунд.
Есть ли способ улучшить эту производительность?
3 ответа
Вы правы, генерация отчета о 56 000 транзакций, генерирующих 224 000 строк (15 МБ), занимает от 20 до 35 секунд. Я действительно думаю, что это довольно приличная производительность, но вы этого не делаете; Хорошо.
Поскольку вы генерируете отчет, используя постоянный формат из неизменной базы данных, вам нужно сделать это только один раз. После этого вы можете использовать кэшированный результат git log
и пропустить трудоемкое поколение. Например:
git log --pretty=format:%H\t%ae\t%an\t%at\t%s --numstat > log-pretty.txt
Вы можете задаться вопросом, сколько времени занимает поиск во всем этом отчете интересующих вас данных. Это достойный вопрос:
$ tail -1 log-pretty.txt
30 0 railties/test/webrick_dispatcher_test.rb
$ time grep railties/test/webrick_dispatcher_test.rb log-pretty.txt
…
30 0 railties/test/webrick_dispatcher_test.rb
real 0m0.012s
…
Не плохо, введение "кэша" сократило время, необходимое с 35+ секунд до десятка миллисекунд. Это почти в 3000 раз быстрее.
Git 2.18 (Q2 2018) улучшит производительность git log:
См. Коммит 902f5a2 (24 марта 2018 г.) Рене Шарфа ( rscharfe
)
Смотрите коммит 0aaf05b, коммит 3d475f4 (22 марта 2018 г.) Деррика Столи ( derrickstolee
)
См. Коммит 626fd98 (22 марта 2018 г.) Брайана М. Карлсон ( bk2204
)
(Объединено Юнио С Хамано - gitster
- в коммите 51f813c, 10 апреля 2018 г.)
sha1_name
: использоватьbsearch_pack()
для сокращенийПри вычислении длин аббревиатур для идентификатора объекта по одному пакетному файлу метод
find_abbrev_len_for_pack()
В настоящее время реализует бинарный поиск.
Это одна из нескольких реализаций.
Одной из проблем этой реализации является то, что она игнорирует таблицу разветвления вpack-index
,Переведите этот двоичный поиск, чтобы использовать существующий
bsearch_pack()
метод, который правильно использует таблицу разветвления.Из-за использования таблицы разветвления вычисление сокращений происходит немного быстрее, чем раньше.
Для полностью перепакованной копии репозитория Linux улучшены следующие команды 'git log':
* git log --oneline --parents --raw
Before: 59.2s
After: 56.9s
Rel %: -3.8%
* git log --oneline --parents
Before: 6.48s
After: 5.91s
Rel %: -8.9%
Тот же Git 2.18 добавляет граф коммитов: предварительно вычисляет и хранит информацию, необходимую для обхода предков, в отдельном файле для оптимизации обхода графа.
См. Коммит 7547b95, коммит 3d5df01, коммит 049d51a, коммит 177722b, коммит 4f2542b, коммит 1b70dfd, коммит 2a2e32b (10 апреля 2018 г.) и коммит f237c8b, коммит 08fd81c, коммит 4ce58ee, коммит ae30d7b, коммит b84f767, коммит ffef2322, коммит ff832 Апрель 2018) Деррик Столи ( derrickstolee
)
(Объединено Юнио С Хамано - gitster
- в комитете b10edb2 от 08 мая 2018 года)
commit
: интегрировать граф коммитов с парсингом коммитовНаучите Git проверять файл графа коммитов для предоставления содержимого struct commit при вызове
parse_commit_gently()
,
Эта реализация удовлетворяет всем постусловиям в struct commit, включая загрузку родителей, корневое дерево и дату фиксации.Если
core.commitGraph
являетсяfalse
, то не проверяйте графические файлы.В тестовом скрипте t5318-commit-graph.sh добавьте
output-matching
условия на графические операции только для чтения.Загружая коммиты из графа вместо разбора буферов коммитов, мы экономим много времени при длительных прогулках.
Вот некоторые результаты производительности для копии репозитория Linux, где "master" имеет 678 653 достижимых коммитов и отстает "
origin/master
на 59 929 коммитов.
| Command | Before | After | Rel % |
|----------------------------------|--------|--------|-------|
| log --oneline --topo-order -1000 | 8.31s | 0.94s | -88% |
| branch -vv | 1.02s | 0.14s | -86% |
| rev-list --all | 5.89s | 1.07s | -81% |
| rev-list --all --objects | 66.15s | 58.45s | -11% |
Чтобы узнать больше о коммит-графе, см. Раздел " Как работает" git log --graph
' Работа? ".
Тот же Git 2.18 (Q2 2018) добавляет ленивое дерево загрузки.
Код научили использовать дублированную информацию, хранящуюся в файле графика фиксации, чтобы узнать имя объекта дерева для фиксации, чтобы избежать открытия и анализа объекта фиксации, когда это имеет смысл.
См. Коммит 279ffad (30 апреля 2018 г.) от SZEDER Gábor ( szeder
)
См. Коммит 7b8a21d, коммит 2e27bd7, коммит 5bb03de, коммит 891435d (06 апреля 2018 г.) Деррика Столи (Derrick Stolee) derrickstolee
)
(Объединено Юнио С Хамано - gitster
- в комитете c89b6e1, 23 мая 2018 года)
commit-graph
: деревья с отложенной загрузкой для коммитовФайл commit-graph обеспечивает быстрый доступ к данным фиксации, включая OID корневого дерева для каждого коммита в графе. При выполнении глубокой прогулки по графу коммитов нам может не потребоваться загрузка большинства деревьев для этих коммитов.
Задержка загрузки объекта дерева для коммита, загруженного из графика, до запроса через
get_commit_tree()
,
Не лениво загружать деревья для коммитов, не включенных в граф, так как для этого требуется повторный анализ, а относительное улучшение производительности, когда деревья не нужны, мало.В репозитории Linux были проведены тесты производительности для следующей команды:
git log --graph --oneline -1000 Before: 0.92s After: 0.66s Rel %: -28.3%
Моей первой мыслью было улучшить ваш ввод-вывод, но я протестировал репозиторий rails с использованием SSD и получил аналогичный результат: 30 секунд.
--numstat
это то, что замедляет все, иначе git-log
можно завершить за 1 секунду даже при форматировании. Выполнение различий стоит дорого, поэтому, если вы можете удалить это из вашего процесса, это значительно ускорит процесс. Возможно, сделайте это после свершившегося факта.
В противном случае, если вы фильтруете записи журнала, используя git-log
Собственные средства поиска, которые уменьшат количество записей, которые нужно сделать различий. Например, git log --grep=foo --numstat
занимает всего одну секунду Они находятся в документах в разделе "Ограничение коммитов". Это может значительно уменьшить количество записей, которые git должен отформатировать. Диапазоны ревизий, фильтры дат, фильтры авторов, очистка сообщений журнала... все это может повысить производительность git-log
на большом репозитории при выполнении дорогостоящей операции.
Есть еще один способ увеличить git log
производительности, и он основан на графиках фиксации, упомянутых в предыдущем ответе.
Git 2.27 (второй квартал 2020 г.) представляет расширение для графа фиксации, чтобы повысить эффективность проверки путей, которые были изменены при каждой фиксации, с использованием фильтров Блума.
См. Фиксацию caf388c (9 апреля 2020 г.) и фиксацию e369698 (30 марта 2020 г.) Деррика Столи (derrickstolee
).
См. Фиксацию d5b873c, фиксацию a759bfa, фиксацию 42e50e7, фиксацию a56b946, фиксацию d38e07b, фиксацию 1217c03, фиксацию 76ffbca (06 апреля 2020 г.) и фиксацию 3d11275, фиксацию f97b932, фиксацию ed591fe, фиксацию f1294ea, фиксацию f52207a, фиксацию 3be7efc (30 марта 2020 г.) по Garima Сингх (singhgarima
).
См. Коммит d21ee7d (30 марта 2020 г.) Джеффа Кинга (peff
).
(Слияние Junio C Hamano -gitster
- в коммите 9b6606f, 01 мая 2020 г.)
revision.c
: используйте фильтры Блума для ускорения обхода ревизий на основе путиПри поддержке: Деррик Столи
При поддержке: СЕДЕР Габор
При поддержке: Джонатан Тан
Подписавшийся: Гарима Сингх
Обход ревизий теперь будет использовать фильтры Блума для коммитов, чтобы ускорить обход ревизий для определенного пути (для вычисления истории для этого пути), если они присутствуют в файле диаграммы коммитов.
Мы загружаем фильтры Блума во время
prepare_revision_walk
step, в настоящее время только при работе с одним указателем пути.
Расширение его для работы с несколькими путями может быть изучено и построено на основе этой серии в будущем.Сравнивая деревья в
rev_compare_trees()
, если фильтр Блума говорит, что файл не отличается между двумя деревьями, нам не нужно вычислять дорогостоящее различие.
Вот где мы получаем прирост производительности.Другой ответ фильтра Блума - ": возможно", и в этом случае мы возвращаемся к полному вычислению различий, чтобы определить, был ли изменен путь в фиксации.
Мы не пытаемся использовать фильтры Блума, когда '
--walk-reflogs
'опция указана.
'--walk-reflogs
'option не проходит по цепочке предков фиксации, как остальные опции.
Включение увеличения производительности при ходячих записях рефлога добавит сложности, и это может быть рассмотрено в следующих сериях.
Прирост производительности: мы протестировали производительность
git log -- <path>
в репозитории git, linux и некоторых внутренних больших репозиториях с множеством путей разной глубины.В репозиториях git и linux:
- мы наблюдали увеличение скорости от 2 до 5 раз.
В большом внутреннем репо с файлами, расположенными на 6-10 уровнях в дереве:
- мы наблюдали увеличение скорости в 10–20 раз, при этом на некоторых маршрутах скорость увеличивалась до 28 раз.
Но: Исправьте (с Git 2.27, Q22020) утечку, замеченную фаззером.
См. Коммит fbda77c (4 мая 2020 г.) Джонатана Тана (jhowtan
).
(Слияние Junio C Hamano -gitster
- в коммите 95875e0, 08 мая 2020 г.)
commit-graph
: избежать утечек памятиПодписано: Джонатан Тан
Рецензент: Деррик Столи
Фаззер работает на точке входа, предоставленной
fuzz-commit-graph.c
обнаружил утечку памяти, когдаparse_commit_graph()
создает структуруbloom_filter_settings
а затем возвращается раньше срока из-за ошибки.Исправьте эту ошибку, всегда сначала освобождая эту структуру (если она существует) перед ранним возвратом из-за ошибки.
Внося это изменение, я также заметил еще одну возможную утечку памяти - когда
BLOOMDATA
кусок предоставляется, но неBLOOMINDEXES
.
Также исправьте эту ошибку.
Git 2.27 (второй квартал 2020 г.) снова улучшает фильтр цветения:
См. Коммит b928e48 (11 мая 2020 г.) Седера Габора (szeder
).
См. Фиксацию 2f6775f, фиксацию 65c1a28, фиксацию 8809328, фиксацию 891c17c (11 мая 2020 г.) и фиксацию 54c337b, фиксацию eb591e4 (1 мая 2020 г.) Деррика Столи (derrickstolee
).
(Слияние Junio C Hamano -gitster
- в коммите 4b1e5e5, 14 мая 2020 г.)
bloom
: устранение дубликатов записей каталогаПодписано: Деррик Столи
При вычислении фильтра Блума измененного пути нам нужно взять файлы, которые изменились из вычисления diff, и извлечь родительские каталоги. Таким образом, путь к каталогу, например "
Documentation
"может соответствовать этим изменениям"Documentation/git.txt
".Однако текущий код плохо справляется с этим процессом.
Пути добавляются в хэш-карту, но мы не проверяем, существует ли уже запись с этим путем.
Это может создать множество повторяющихся записей и привести к тому, что фильтр будет иметь гораздо большую длину, чем следовало бы.
Это означает, что фильтр более разреженный, чем предполагалось, что способствует увеличению количества ложных срабатываний, но при этом занимает много места.Правильно использовать
hashmap_get()
передhashmap_add()
.
Также не забудьте включить функцию сравнения, чтобы их можно было правильно сопоставить.Это влияет на тест в
t0095-bloom.sh
.
В этом есть смысл, внутри десять изменений "smallDir
"поэтому общее количество путей в фильтре должно быть 11.
В результате потребуется 11 * 10 бит, а при 8 битах на байт это даст 14 байтов.
С Git 2.28 (3 квартал 2020 г.) "git log -L...
"now" использует информацию "какие пути затронуты этим коммитом?", хранящуюся в системе коммитов.
Для этого используется фильтр цветения.
См. Commit f32dde8 (11 мая 2020 г.) Деррик Столи (derrickstolee
).
См. Фиксацию 002933f, фиксацию 3cb9d2b, фиксацию 48da94b, фиксацию d554672 (11 мая 2020 г.) СЕДЕР Габор (szeder
).
(Слияние Junio C Hamano -gitster
- в коммите c3a0282, 09 июня 2020 г.)
line-log
: интегрировать сchanged-path
Фильтры БлумаПодписано: Деррик Столи
Предыдущие изменения в механизме линейного журнала были направлены на то, чтобы первый результат появился быстрее. Это было достигнуто тем, что больше не просматривали всю историю коммитов перед возвратом первых результатов.
Есть еще один способ повысить производительность: большинство пешеходов совершает гораздо быстрее. Давайте воспользуемся фильтрами Блума с измененным путем, чтобы сократить время, затрачиваемое на вычисление различий.Поскольку
line-log
вычисление требует открытия капли и проверкиcontent-diff
, есть еще много необходимых вычислений, которые нельзя заменить фильтрами Блума с измененным путем.
Часть, которую мы можем уменьшить, наиболее эффективна при проверке истории файла, который находится глубоко в нескольких каталогах, и эти каталоги часто изменяются.
В этом случае вычисление для проверки того, является ли фиксацияTREESAME
своему первому родителю занимает большую часть времени.
Это созрело для улучшения с фильтрами Блума с измененным путем.Мы должны гарантировать, что
prepare_to_use_bloom_filters()
называется вrevision.c
таким образомbloom_filter_settings
загружаются в структуруrev_info
из коммит-графа.
Конечно, некоторые случаи все же запрещены, но вline-log
если путь указан не так, как обычно.Поскольку можно запросить несколько путей и сегментов, мы вычисляем структуру
bloom_key
данные динамически во время обхода фиксации. Это, вероятно, можно улучшить, но добавляет сложность кода, которая в настоящее время не имеет значения.Есть два случая, о которых следует позаботиться: коммиты слиянием и "обычные" коммиты.
- У коммитов слияния есть несколько родителей, но если мы ДРЕВНИМ по отношению к нашему первому родителю в каждом диапазоне, то перекладываем ответственность за все диапазоны на первого родителя.
- Обычные коммиты имеют одинаковые условия, но все они выполняются немного по-разному в
process_ranges_[merge|ordinary]_commit()
методы.Проверяя, может ли фильтр Bloom с измененным путем гарантировать TREESAME, мы можем избежать затрат на различие между деревьями. Если фильтр говорит "вероятно, изменен", то нам нужно запустить tree-diff, а затем blob-diff, если было реальное редактирование.
Репозиторий ядра Linux является хорошей площадкой для тестирования заявленных здесь улучшений производительности.
Есть два разных случая для тестирования:
- Первый случай - "вся история", когда мы выводим всю историю в
/dev/null
чтобы узнать, сколько времени потребуется для вычисления полной истории строчного журнала.- Второй - это случай "первого результата", где мы находим, сколько времени требуется, чтобы отобразить первое значение, которое является индикатором того, как быстро пользователь увидит ответы, ожидая на терминале.
Чтобы проверить, я выбрал пути, которые наиболее часто менялись в топ-10000 коммитов, используя эту команду ( украденную из Stackru):
git log --pretty=format: --name-only -n 10000 | sort | \ uniq -c | sort -rg | head -10
что приводит к
121 MAINTAINERS 63 fs/namei.c 60 arch/x86/kvm/cpuid.c 59 fs/io_uring.c 58 arch/x86/kvm/vmx/vmx.c 51 arch/x86/kvm/x86.c 45 arch/x86/kvm/svm.c 42 fs/btrfs/disk-io.c 42 Documentation/scsi/index.rst
(вместе с фальшивым первым результатом).
Похоже, что путьarch/x86/kvm/svm.c
был переименован, поэтому мы игнорируем эту запись. Это оставляет следующие результаты для реального времени команды:| | Entire History | First Result | | Path | Before | After | Before | After | |------------------------------|--------|--------|--------|--------| | MAINTAINERS | 4.26 s | 3.87 s | 0.41 s | 0.39 s | | fs/namei.c | 1.99 s | 0.99 s | 0.42 s | 0.21 s | | arch/x86/kvm/cpuid.c | 5.28 s | 1.12 s | 0.16 s | 0.09 s | | fs/io_uring.c | 4.34 s | 0.99 s | 0.94 s | 0.27 s | | arch/x86/kvm/vmx/vmx.c | 5.01 s | 1.34 s | 0.21 s | 0.12 s | | arch/x86/kvm/x86.c | 2.24 s | 1.18 s | 0.21 s | 0.14 s | | fs/btrfs/disk-io.c | 1.82 s | 1.01 s | 0.06 s | 0.05 s | | Documentation/scsi/index.rst | 3.30 s | 0.89 s | 1.46 s | 0.03 s |
Стоит отметить, что наименьшее ускорение происходит для файла MAINTAINERS, который:
- часто редактируется,
- низко в иерархии каталогов, и
- довольно большой файл.
Все эти моменты приводят к тому, что вы тратите больше времени на сравнение больших двоичных объектов и меньше времени на изучение дерева.
Тем не менее, мы видим некоторое улучшение в этом случае и значительное улучшение в других случаях.
Ускорение в 2-4 раза, вероятно, является более типичным случаем, чем небольшое изменение в 5% для этого файла.
В Git 2.29 (Q4 2020) фильтр Блума с измененным путем улучшен с использованием идей из независимой реализации.
См. Commit 7fbfe07, commit bb4d60e, commit 5cfa438, commit 2ad4f1a, commit fa79653, commit 0ee3cb8, commit 1df15f8, commit 6141cdf, commit cb9daf1, commit 35a9f1e (05 Jun 2020) by SZEDER Gábor (szeder
).
(Слияние Junio C Hamano -gitster
- в коммите de6dda0, 30 июл 2020)
commit-graph
: упрощатьparse_commit_graph()
#1Подписал: СЕДЕР Габор.
Подписал: Деррик Столи.
В то время как мы перебираем все записи в таблице поиска фрагментов, мы следим за тем, чтобы мы не пытались прочитать за пределами файла mmap-ed commit-graph, и проверяем на каждой итерации, что идентификатор фрагмента и смещение, которое мы собираемся чтение все еще находится в области памяти mmap-ed. Однако эти проверки на каждой итерации на самом деле не нужны, потому что количество фрагментов в файле графа фиксации уже известно до этого цикла из только что проанализированного заголовка графа фиксации.
Итак, давайте проверим, что файл графика фиксации достаточно велик для всех записей в таблице поиска фрагментов, прежде чем мы начнем итерацию по этим записям, и отбросим эти проверки для каждой итерации.
При этом учитывайте размер всего, что необходимо для правильного файла графика фиксации, то есть размер заголовка, размер обязательного фрагмента OID Fanout, а также размер подписи в трейлере..Обратите внимание, что это также требует изменения сообщения об ошибке. Se
А также commit-graph
:
Таблица поиска фрагментов хранит начальное смещение фрагментов в файле графика фиксации, а не их размеры.
Следовательно, размер фрагмента можно рассчитать только путем вычитания его смещения из смещения последующего фрагмента (или конечной метки).
В настоящее время это реализовано немного сложным образом: когда мы перебираем записи в таблице поиска фрагментов, мы проверяем идентификатор каждого фрагмента и сохраняем его начальное смещение, затем мы проверяем идентификатор последнего увиденного фрагмента и вычисляем его размер, используя его ранее сохраненное смещение.
На данный момент есть только один чанк, для которого мы вычисляем его размер, но эта серия патчей добавит больше, и повторяющиеся проверки идентификатора чанка не так хороши.Вместо этого давайте заранее прочитаем смещение следующего фрагмента на каждой итерации, чтобы мы могли сразу вычислить размер каждого фрагмента, прямо там, где мы сохраняем его начальное смещение.