git gc --aggressive vs git repack

88

Szukam sposobów na zmniejszenie rozmiaru gitrepozytorium. Wyszukiwanie prowadzi mnie do git gc --aggressivewiększości przypadków. Czytałem też, że nie jest to preferowane podejście.

Czemu? co powinienem wiedzieć, jeśli biegam gc --aggressive?

git repack -a -d --depth=250 --window=250jest zalecane powyżej gc --aggressive. Czemu? Jak repackzmniejszyć rozmiar repozytorium? Nie mam też pewności co do flag --depthi --window.

Co mam wybierać między gca repack? Kiedy stosować gci repack?

Ajith R Nayak
źródło

Odpowiedzi:

76

Dziś nie ma różnicy: git gc --aggressivedziała zgodnie z sugestią Linusa z 2007 roku; patrz poniżej. Począwszy od wersji 2.11 (Q4 2016), git domyślnie przyjmuje głębokość 50. Okno o rozmiarze 250 jest dobre, ponieważ skanuje większą część każdego obiektu, ale głębokość na 250 jest zła, ponieważ każdy łańcuch odnosi się do bardzo głębokiego starego obiekty, co spowalnia wszystkie przyszłe operacje git w celu minimalnego zmniejszenia wykorzystania dysku.


Tło historyczne

Linus zasugerował (zobacz poniżej pełny post na liście mailingowej), używając go git gc --aggressivetylko wtedy, gdy masz, jego słowami, „ naprawdę zły pakiet” lub „naprawdę strasznie złe delty”, jednak „prawie zawsze, w innych przypadkach jest to naprawdę bardzo złe rzecz do zrobienia." Rezultat może nawet pozostawić repozytorium w gorszym stanie niż na początku!

Polecenie, które sugeruje, aby zrobić to właściwie po zaimportowaniu „długiej i zawiłej historii”, brzmi

git repack -a -d -f --depth=250 --window=250

Ale to zakłada, że usunąłeś już niechcianą gunk z historii repozytorium i postępowałeś zgodnie z listą kontrolną dotyczącą zmniejszania repozytorium znalezionego w git filter-branchdokumentacji .

git-filter-branch może być użyty do pozbycia się podzbioru plików, zwykle z kombinacją --index-filteri --subdirectory-filter. Ludzie oczekują, że wynikowe repozytorium będzie mniejsze od oryginału, ale potrzebujesz jeszcze kilku kroków, aby je zmniejszyć, ponieważ Git bardzo się stara, aby nie stracić obiektów, dopóki tego nie powiesz. Najpierw upewnij się, że:

  • Naprawdę usunąłeś wszystkie warianty nazwy pliku, jeśli obiekt blob został przeniesiony w czasie jego życia. git log --name-only --follow --all -- filenamemoże pomóc w znalezieniu zmian.

  • Naprawdę przefiltrowałeś wszystkie referencje: używaj --tag-name-filter cat -- --allpodczas dzwonienia git filter-branch.

Następnie są dwa sposoby na uzyskanie mniejszego repozytorium. Bezpieczniejszym sposobem jest klonowanie, dzięki czemu oryginał pozostaje nienaruszony.

  • Sklonuj to za pomocą git clone file:///path/to/repo. Klon nie będzie miał usuniętych obiektów. Zobacz git-clone. (Zwróć uwagę, że klonowanie zwykłą ścieżką po prostu łączy wszystko!)

Jeśli naprawdę nie chcesz go klonować, z jakiegokolwiek powodu, sprawdź zamiast tego poniższe punkty (w tej kolejności). Jest to bardzo destrukcyjne podejście, więc wykonaj kopię zapasową lub wróć do jej klonowania. Zostałeś ostrzeżony.

  • Usuń oryginalne odwołania z kopii zapasowej utworzonej przez git-filter-branch: powiedz

    git for-each-ref --format="%(refname)" refs/original/ |
      xargs -n 1 git update-ref -d
    
  • Przedawnij wszystkie reflogi z git reflog expire --expire=now --all.

  • Zbierz wszystkie obiekty bez odwołań za pomocą git gc --prune=now(lub jeśli nie jesteś na git gctyle nowy, aby obsługiwać argumenty --prune, użyj git repack -ad; git prunezamiast tego).


Date: Wed, 5 Dec 2007 22:09:12 -0800 (PST)
From: Linus Torvalds <torvalds at linux-foundation dot org>
To: Daniel Berlin <dberlin at dberlin dot org>
cc: David Miller <davem at davemloft dot net>,
    ismail at pardus dot org dot tr,
    gcc at gcc dot gnu dot org,
    git at vger dot kernel dot org
Subject: Re: Git and GCC
In-Reply-To: <[email protected]>
Message-ID: <[email protected]>
References: <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>

W czwartek, 6 grudnia 2007, Daniel Berlin napisał:

Właściwie okazuje się, że git-gc --aggressiveczasami pakuje się pliki, niezależnie od tego, czy dokonałeś konwersji z repozytorium SVN, czy nie.

Absolutnie. git --aggressivejest przeważnie głupi. Jest to przydatne tylko w przypadku „Wiem, że mam naprawdę kiepski pakiet i chcę odrzucić wszystkie złe decyzje dotyczące pakowania”.

Aby to wyjaśnić, warto wyjaśnić (prawdopodobnie jesteś tego świadomy, ale i tak przejdę przez podstawy), jak działają łańcuchy delta git i jak bardzo różnią się od większości innych systemów.

W innych SCM łańcuch delta jest zwykle naprawiony. Może to być „do przodu” lub „do tyłu” i może ewoluować nieco podczas pracy z repozytorium, ale ogólnie jest to łańcuch zmian w pojedynczym pliku reprezentowanym jako pewnego rodzaju pojedynczy obiekt SCM. W CVS jest to oczywiście *,vplik, a wiele innych systemów robi raczej podobne rzeczy.

Git również obsługuje łańcuchy delta, ale robi to dużo bardziej „luźno”. Nie ma stałej jednostki. Różnice są generowane dla dowolnej innej losowej wersji, którą git uważa za dobrego kandydata na deltę (z różnymi dość skutecznymi heurystykami) i nie ma absolutnie żadnych twardych reguł grupowania.

Generalnie jest to bardzo dobra rzecz. Jest to dobre z różnych powodów koncepcyjnych ( np. Git wewnętrznie nigdy nie musi nawet przejmować się całym łańcuchem wersji - tak naprawdę nie myśli w kategoriach delt), ale jest również świetny, ponieważ pozbycie się nieelastycznych reguł delta oznacza że git nie ma żadnych problemów ze scalaniem dwóch plików, na przykład - po prostu nie ma żadnych *,v„plików wersji”, które mają jakieś ukryte znaczenie.

Oznacza to również, że wybór delt jest kwestią znacznie bardziej otwartą. Jeśli ograniczysz łańcuch delta do tylko jednego pliku, naprawdę nie masz dużego wyboru, co zrobić z deltami, ale w git naprawdę może to być zupełnie inny problem.

I tu pojawia się naprawdę źle nazwany --aggressive. Chociaż git generalnie próbuje ponownie wykorzystać informacje delta (ponieważ jest to dobry pomysł i nie marnuje czasu procesora na ponowne znajdowanie wszystkich dobrych delt, które znaleźliśmy wcześniej), czasami chcesz powiedzieć „zacznijmy wszystko od nowa, z pustą planszą, zignoruj ​​wszystkie poprzednie informacje o różnicach i spróbuj wygenerować nowy zestaw delt”.

Tak --aggressivenaprawdę nie chodzi o bycie agresywnym, ale o marnowanie czasu procesora na ponowne podejmowanie decyzji, którą już podjęliśmy wcześniej!

Czasami to dobrze. W szczególności niektóre narzędzia do importowania mogą generować naprawdę strasznie złe delty. Wszystko, co używagit fast-import , prawdopodobnie nie ma świetnego układu delta, więc warto powiedzieć: „Chcę zacząć od czystej karty”.

Ale prawie zawsze, w innych przypadkach, jest to naprawdę złe. Spowoduje to stratę czasu procesora, a zwłaszcza jeśli wcześniej wykonałeś dobrą robotę przy deltaowaniu, wynik końcowy nie będzie ponownie wykorzystywał wszystkich dobrych delt, które już znalazłeś, więc tak naprawdę skończysz z dużo gorszy wynik końcowy!

Wyślę łatkę do Junio, aby po prostu usunąć git gc --aggressive dokumentację. Może być przydatna, ale generalnie jest przydatna tylko wtedy, gdy naprawdę rozumiesz, na bardzo głębokim poziomie, co robi, a ta dokumentacja ci w tym nie pomaga.

Generalnie robienie przyrostowe git gcjest właściwym podejściem i jest lepsze niż robienie git gc --aggressive. Będzie ponownie używać starych delt, a kiedy nie można ich znaleźć (powód wykonywania przyrostowych GC w pierwszej kolejności!), Utworzy nowe.

Z drugiej strony, z pewnością prawdą jest, że „początkowy import długiej i zawiłej historii” to moment, w którym warto poświęcić dużo czasu na znalezienie naprawdę dobrych delt. Wtedy każdy użytkownik kiedykolwiek (o ile nie użyje go git gc --aggressivedo cofnięcia!) Otrzyma korzyść z tego jednorazowego zdarzenia. Dlatego szczególnie w przypadku dużych projektów z długą historią, prawdopodobnie warto wykonać dodatkową pracę, nakazując kodowi znajdującemu delta oszaleć.

Tak więc odpowiednikiem git gc --aggressive- ale wykonanym prawidłowo - jest zrobienie (z dnia na dzień) czegoś takiego

git repack -a -d --depth=250 --window=250

gdzie ta kwestia głębi dotyczy tego, jak głębokie mogą być łańcuchy delta (wydłuż je dla starej historii - jest to warte przestrzeni narzutu), a sprawa okna dotyczy tego, jak duże okno obiektu chcemy przeskanować każdy kandydat na deltę.

I tutaj możesz chcieć dodać -fflagę (która oznacza „porzucić wszystkie stare delty”, ponieważ teraz faktycznie próbujesz upewnić się, że ta faktycznie znajduje dobrych kandydatów.

A potem zajmie to wieczność i jeden dzień ( czyli „zrób to z dnia na dzień”). Ale końcowy rezultat jest taki, że wszyscy poniżej tego repozytorium otrzymają znacznie lepsze pakiety, bez konieczności poświęcania na to żadnego wysiłku.

          Linus
Greg Bacon
źródło
2
Twój komentarz dotyczący głębi jest nieco zagmatwany. Na początku zamierzałem narzekać, że się mylisz, że agresywność może znacznie przyspieszyć repozytorium git. Po przeprowadzeniu agresywnego czyszczenia śmieci OGROMNE repozytorium, które zajęło pięć minut, aby zrobić status gita, zostało zredukowane do sekund. Ale potem zdałem sobie sprawę, że nie chodziło Ci o to, że agresywne gc spowolniło repozytorium, ale po prostu o niezwykle duży rozmiar głębokości.
user6856
57

Kiedy powinienem używać Gc & Repack?

Jak wspomniałem w artykuleGit Garbage Collection nie wydaje się w pełni działać ”, sam a git gc --aggressivenie jest ani wystarczający, ani nawet wystarczający.
I, jak wyjaśnię poniżej , często nie są potrzebne.

Najskuteczniejszą kombinacją byłoby dodanie git repack, ale także git prune:

git gc
git repack -Ad      # kills in-pack garbage
git prune           # kills loose garbage

Uwaga: Git 2.11 (Q4 2016) ustawi domyślną gc aggressivegłębokość na 50

Zobacz commit 07e7dbf (11 sierpnia 2016) autorstwa Jeffa Kinga ( peff) .
(Scalone przez Junio ​​C Hamano - gitster- w zobowiązaniu 0952ca8 , 21 września 2016 r.)

gc: domyślna agresywna głębokość do 50

git gc --aggressive” służy do ograniczenia długości łańcucha delta do 250, co jest zbyt głębokie, aby uzyskać dodatkowe oszczędności miejsca i ma negatywny wpływ na wydajność środowiska wykonawczego.
Limit został zmniejszony do 50.

Podsumowanie jest następujące: obecna domyślna wartość 250 nie oszczędza dużo miejsca i kosztuje procesor. To nie jest dobry kompromis.

Flaga „ --aggressive” służy do wykonywania git-gctrzech czynności:

  1. użyj " -f", aby wyrzucić istniejące delty i przeliczyć od zera
  2. użyj "--window = 250", aby dokładniej sprawdzić delty
  3. użyj "--depth = 250", aby utworzyć dłuższe łańcuchy delta

Elementy (1) i (2) dobrze pasują do „agresywnego” przepakowywania.
Proszą repack, aby wykonał więcej obliczeń w nadziei na uzyskanie lepszego pakietu. Ponosisz koszty podczas przepakowywania, a inne operacje widzą tylko korzyści.

Pozycja (3) nie jest tak jasna.
Zezwolenie na dłuższe łańcuchy oznacza mniej ograniczeń dla delt, co oznacza potencjalnie znalezienie lepszych i zaoszczędzenie miejsca.
Ale oznacza to również, że operacje, które uzyskują dostęp do delt, muszą wykonywać dłuższe łańcuchy, co wpływa na ich wydajność.
Jest to więc kompromis i nie jest jasne, czy kompromis jest nawet dobry.

(Zobacz zobowiązanie do nauki )

Widać, że oszczędność procesora w przypadku zwykłych operacji rośnie, gdy zmniejszamy głębokość.
Ale widzimy również, że oszczędność miejsca nie jest tak duża, gdy głębokość rośnie. Oszczędność 5-10% między 10 a 50 jest prawdopodobnie warta kompromisu z procesorem. Oszczędność 1%, aby przejść z 50 do 100, lub kolejne 0,5%, aby przejść ze 100 do 250, prawdopodobnie nie jest.


Mówiąc o oszczędzaniu procesora, " git repack" nauczył się akceptować--threads=<n> opcję i przekazywać ją do obiektów paczki.

Zobacz commit 40bcf31 (26 kwietnia 2017) autorstwa Junio ​​C Hamano ( gitster) .
(Scalenie przez Junio ​​C Hamano - gitster- w zatwierdzeniu 31fb6f4 , 29 maja 2017)

przepakuj: zaakceptuj --threads=<n>i przekaż dopack-objects

Robimy to już dla --window=<n>i --depth=<n>; pomoże to, gdy użytkownik chce wymusić --threads=1powtarzalne testy bez wpływu na wyścigi wielu wątków.

VonC
źródło
3
Wspomniałem o wątku Linusa w linku "Git Garbage collection nie działa w pełni"
VonC,
1
Dzięki za tę nowoczesną aktualizację! Każda inna odpowiedź jest stara. Teraz widzimy, że git gc --aggressivezostało to naprawione dwukrotnie: po pierwsze, aby zrobić to, co Linus zasugerował w 2007 roku jako „lepszą metodę pakowania”. A potem w Git 2.11, aby uniknąć nadmiernej głębokości obiektów, którą zasugerował Linus, ale która okazała się szkodliwa (spowalnia wszystkie przyszłe operacje Gita i nie oszczędza miejsca, o którym warto mówić).
gw0
git gc, a następnie git repack -Ad i git prune zwiększają rozmiar mojego repozytorium ... dlaczego?
devops,
@devops Nie jestem pewien: jakiej wersji Git używasz? Możesz zadać nowe pytanie (z dodatkowymi szczegółami, takimi jak system operacyjny, ogólny rozmiar repozytorium, ...)
VonC
man git-repackmówi dla -d: `Uruchom także git prune -pack, aby usunąć zbędne luźne pliki obiektowe. 'Czy git pruneteż robi to? man git-prunemówi In most cases, users should run git gc, which calls git prune., więc po co to wszystko git gc? Czy nie byłoby lepiej lub wystarczy użyć tylko git repack -Ad && git gc?
Jakob
14

Problem z git gc --aggressive polega na tym, że nazwa opcji i dokumentacja są mylące.

Jak sam Linus wyjaśnia w tym mailu , git gc --aggressivezasadniczo robi to:

Chociaż git generalnie próbuje ponownie wykorzystać informacje delta (ponieważ jest to dobry pomysł i nie marnuje czasu procesora na ponowne znajdowanie wszystkich dobrych różnic, które znaleźliśmy wcześniej), czasami chcesz powiedzieć „zacznijmy od początku, z pusta plansza i zignoruj ​​wszystkie poprzednie informacje o różnicach i spróbuj wygenerować nowy zestaw różnic ”.

Zwykle nie ma potrzeby ponownego obliczania delt w git, ponieważ git określa te delty bardzo elastycznie. Ma to sens tylko wtedy, gdy wiesz, że masz naprawdę, naprawdę złe delty. Jak wyjaśnia Linus, głównie narzędzia, które wykorzystujągit fast-import do tej kategorii należą .

W większości przypadków git wykonuje całkiem niezłą robotę przy określaniu użytecznych delt, a używanie git gc --aggressivepozostawi delty, które są potencjalnie nawet gorsze, a jednocześnie marnują dużo czasu procesora.


Linus kończy swój list wnioskiem, że git repackz dużym --depthi --windowjest lepszym wyborem w większości przypadków; szczególnie po zaimportowaniu dużego projektu i chcesz się upewnić, że git znajdzie dobre delty.

Tak więc odpowiednikiem git gc --aggressive- ale wykonanym prawidłowo - jest zrobienie (z dnia na dzień) czegoś takiego

git repack -a -d --depth=250 --window=250

gdzie ta kwestia głębi dotyczy tego, jak głębokie mogą być łańcuchy delta (wydłuż je dla starej historii - jest to warte przestrzeni narzutu), a kwestia okna dotyczy tego, jak duże okno obiektu chcemy przeskanować każdy kandydat na deltę.

I tutaj możesz chcieć dodać -fflagę (która oznacza „porzuć wszystkie stare delty”, ponieważ teraz faktycznie próbujesz upewnić się, że ta rzeczywiście znajduje dobrych kandydatów.

Sascha Wolf
źródło
8

Uwaga. Nie uruchamiaj git gc --agressivez repozytorium, które nie jest zsynchronizowane ze zdalnym, jeśli nie masz kopii zapasowych.

Ta operacja odtwarza delty od zera i może doprowadzić do utraty danych, jeśli zostanie bezpiecznie przerwana.

Na moim komputerze z 8 GB agresywnego gc zabrakło pamięci w repozytorium 1 GB z 10 000 małymi zatwierdzeniami. Kiedy OOM killer zakończył proces gita - zostawił mnie z prawie pustym repozytorium, przetrwało tylko działające drzewo i kilka delt.

Oczywiście nie była to jedyna kopia repozytorium, więc po prostu ją odtworzyłem i ściągnąłem ze zdalnego (pobieranie nie działało na zepsutym repozytorium i zablokowało się na kroku `` rozwiązywania delt '' kilka razy próbowałem to zrobić), ale jeśli twoje repozytorium jest Lokalne repozytorium jednego programisty bez pilotów - najpierw wykonaj kopię zapasową.

Sage Pointer
źródło
5

Uwaga: uważaj na używanie git gc --aggressive, jak wyjaśnia Git 2.22 (Q2 2019).

Zobacz zatwierdzenie 0044f77 , zatwierdzenie daecbf2 , zatwierdzenie 7384504 , zatwierdzenie 22d4e3b , zatwierdzenie 080a448 , zatwierdzenie 54d56f5 , zatwierdzenie d257e0f , zatwierdzenie b6a8d09 (07 kwietnia 2019) i zatwierdzenie fc559fb , zatwierdzenie cf9cd77 , zatwierdzenie b11e856 (22 marca 2019) do (Börjarm avar) do (Börjarm ) .
(Scalone przez Junio ​​C Hamano - gitster- w zobowiązaniu ac70c53 , 25 kwietnia 2019 r.)

gc docs: bagatelizuj użyteczność --aggressive

Istniejące gc --aggressivedokumenty „ ” nie zalecają użytkownikom regularnego ich uruchamiania.
Osobiście rozmawiałem z wieloma użytkownikami, którzy potraktowali te dokumenty jako poradę dotyczącą korzystania z tej opcji i zwykle jest to (głównie) strata czasu .

Wyjaśnijmy więc, co to naprawdę robi i pozwól użytkownikowi wyciągnąć własne wnioski.

Wyjaśnijmy również, że „Skutki [...] są trwałe”, parafrazując krótką wersję wyjaśnienia Jeffa Kinga .

Oznacza to, że dokumentacja git-gc zawiera teraz :

AGRESYWNY

Gdy --aggressiveopcja git-repackzostanie podana , zostanie wywołana z -fflagą, która z kolei zostanie przekazana --no-reuse-deltado git-pack-objects .
Spowoduje to odrzucenie wszelkich istniejących delt i ponowne ich obliczenie, kosztem spędzenia znacznie więcej czasu na przepakowaniu.

Skutki tego są przeważnie trwałe, np. Gdy paczki i luźne przedmioty są łączone w jedną paczkę, istniejące delty w tej paczce mogą zostać ponownie wykorzystane, ale są też różne przypadki, w których możemy wybrać nieoptymalną deltę z nowszej zamiast tego pakuj.

Ponadto dostarczanie --aggressiveulepszy opcje --depthi --windowprzekazane do git-repack.
Zobacz ustawienia gc.aggressiveDepthi gc.aggressiveWindowponiżej.
Używając większego rozmiaru okna, jesteśmy bardziej skłonni znaleźć bardziej optymalne delty.

Prawdopodobnie nie warto używać tej opcji na danym repozytorium bez przeprowadzania na nim dopasowanych testów wydajności .
Zajmuje to dużo więcej czasu, a wynikająca z tego optymalizacja przestrzeni / delta może być tego warta, ale nie musi. Zrezygnowanie z tego w ogóle jest właściwą kompromisem dla większości użytkowników i ich repozytoriów.

Oraz ( zatwierdzenie 080a448 ):

gcdokumenty: zwróć uwagę na --aggressivewpływ --windowi--depth

Od 07e7dbf ( gc: domyślna agresywna głębia do 50, 2016-08-11, Git v2.10.1) w nieco mylący sposób używamy tej samej głębokości --aggressive, co domyślnie.

Jak zauważono w tym zatwierdzeniu, które ma sens, błędem było ustawienie większej głębi jako domyślnej dla „agresywnej”, a tym samym zaoszczędzenie miejsca na dysku kosztem wydajności czasu wykonania, co jest zwykle przeciwieństwem kogoś, kto chciałby „agresywnego gc” chce.

VonC
źródło