Как улучшить производительность 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_walkstep, в настоящее время только при работе с одним указателем пути.
Расширение его для работы с несколькими путями может быть изучено и построено на основе этой серии в будущем.

Сравнивая деревья в 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:

Таблица поиска фрагментов хранит начальное смещение фрагментов в файле графика фиксации, а не их размеры.
Следовательно, размер фрагмента можно рассчитать только путем вычитания его смещения из смещения последующего фрагмента (или конечной метки).
В настоящее время это реализовано немного сложным образом: когда мы перебираем записи в таблице поиска фрагментов, мы проверяем идентификатор каждого фрагмента и сохраняем его начальное смещение, затем мы проверяем идентификатор последнего увиденного фрагмента и вычисляем его размер, используя его ранее сохраненное смещение.
На данный момент есть только один чанк, для которого мы вычисляем его размер, но эта серия патчей добавит больше, и повторяющиеся проверки идентификатора чанка не так хороши.

Вместо этого давайте заранее прочитаем смещение следующего фрагмента на каждой итерации, чтобы мы могли сразу вычислить размер каждого фрагмента, прямо там, где мы сохраняем его начальное смещение.

Другие вопросы по тегам