Как ускорить/распараллелить загрузки подмодулей git с помощью git clone --recursive?

Клонирование git репозиториев с большим количеством подмодулей занимает очень много времени. В следующем примере ~ 100 подмодулей

git clone --recursive https://github.com/Whonix/Whonix

Git клонирует их один за другим. Принимается намного дольше, чем требуется. Позвольте сделать (вероятное) предположение, что и клиент, и сервер имеют достаточные ресурсы для одновременного ответа на несколько (параллельных) запросов.

Как ускорить/распараллелить загрузки подмодулей git с помощью git clone --recursive?

Ответ 1

Когда я запускаю вашу команду, для загрузки 68 Мбайт требуется время на 338 секунд.

Со следующей программой Python, которая полагается на установленный GNU,

#! /usr/bin/env python
# coding: utf-8

from __future__ import print_function

import os
import subprocess

jobs=16

modules_file = '.gitmodules'

packages = []

if not os.path.exists('Whonix/' + modules_file):
    subprocess.call(['git', 'clone', 'https://github.com/Whonix/Whonix'])

os.chdir('Whonix')

# get list of packages from .gitmodules file
with open(modules_file) as ifp:
    for line in ifp:
        if not line.startswith('[submodule '):
            continue
        package = line.split(' "', 1)[1].split('"', 1)[0]
        #print(package)
        packages.append(package)

def doit():
    p = subprocess.Popen(['parallel', '-N1', '-j{0}'.format(jobs),
                          'git', 'submodule', 'update', '--init',
                          ':::'],
                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    res = p.communicate('\n'.join(packages))
    print(res[0])
    if res[1]:
        print("error", res[1])
    print('git exit value', p.returncode)
    return p.returncode

# sometimes one of the updates interferes with the others and generate lock
# errors, so we retry
for x in range(10):
    if doit() == 0:
        print('zero exit from git after {0} times'.format(x+1))
        break
else:
    print('could not get a non-zero exit from git after {0} times'.format(
          x+1))

время сокращается до 45 секунд (в той же системе я не выполнял несколько прогонов, чтобы усреднить флуктуации).

Чтобы проверить, все ли в порядке, я "сравнил" извлеченные файлы с помощью:

find Whonix -name ".git" -prune -o -type f -print0 | xargs -0 md5sum > /tmp/md5.sum

в одном каталоге и

md5sum -c /tmp/md5sum 

в другом каталоге и наоборот.

Ответ 2

С git 2.8 (Q12016) вы сможете инициировать выборку субмодулей... параллельно!

См. коммит fbf7164 (16 декабря 2015 г.) от Джонатана Нидера (artagnon).
См. commit 62104ba, commit fe85ee6, commit c553c72, commit bfb6b53, commit b4e04fb, commit 1079c4b (16 декабря 2015 г.) Стефан Беллер (stefanbeller).
(Merged by Junio C Hamano -- [TG42] -- in commit 187c0d3, 12 Jan 2016)

Добавьте фреймворк, чтобы порождать группу процессов параллельно, и используйте чтобы запустить "git fetch --recurse-submodules" параллельно.

Для этого у git fetch есть новая опция:

-j, --jobs=<n>

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

Пример:

git fetch --recurse-submodules -j2

Большая часть этой новой функции содержится в коммите c553c72 (16 декабря 2015 г.) Стефаном Беллером (stefanbeller).

run-command: добавить асинхронный параллельный дочерний процессор

Это позволяет запускать внешние команды параллельно с упорядоченным выводом на stderr.

Если мы выполняем внешние команды параллельно, мы не можем напрямую передать вывод к нашему stdout/err, поскольку это смешалось бы. Таким образом, каждый выход процесса будет течь через pipeу, которую мы буферизируем. Один подпроцесс может быть напрямую направил в stdout/err для обратной связи с низкой задержкой для пользователя.


Обратите внимание, что до Git 2.24 (Q4 2019) "git fetch --jobs=<n>" разрешал <n> параллельные задания при извлечении подмодулей, но это не относится к "git fetch --multiple", который выбирает из нескольких удаленных репозиториев.
Теперь это так.

См. коммит d54dea7 (05 октября 2019 г.) от Йоханнеса Шинделина (dscho).
(Merged by Junio C Hamano -- [TG413] -- in commit d96e31e, 15 Oct 2019)

fetch: пусть --jobs=<n> тоже распараллелит --multiple

Signed-off-by: Johannes Schindelin

До сих пор --jobs=<n> распараллеливает только выборки/клоны субмодулей, а не --multiple выборки, что не интуитивно понятно, учитывая, что имя опции ничего не говорит о подмодулях в частности.

Давайте изменим это.
Этот патч также распараллеливает выборки с нескольких пультов.

Для обратной совместимости (и для подготовки к случаю использования, когда субмодулям и множественным удаленным выборкам могут потребоваться разные пределы распараллеливания):

  • настройка config submodule.fetchJobs все еще контролирует только часть субмодуля git fetch,
  • в то время как недавно представленная настройка fetch.parallel управляет обоими (но может быть переопределена для подмодулей с помощью submodule.fetchJobs).