web-dev-qa-db-fra.com

Quelle est la différence entre `git diff --patience` et` git diff --histogram`?

Cette question précédente a demandé les différences entre 4 stratégies différentes de diff Git, mais la seule différence qui a été expliquée était la différence entre myers et patience, ce qui est assez bien expliqué ailleurs .

Comment fonctionne la stratégie histogram? Qu'est-ce qui le différencie de patience? La page de manuel git-diff dit seulement qu'elle "étend l'algorithme de patience pour" prendre en charge les éléments communs à faible occurrence "." D'autres pages mentionnent qu'il est plus rapide et qu'il provient de JGit, mais ils n'expliquent pas où ou comment son algorithme ou ses résultats différeront de patience.

Où puis-je trouver une description de l'algorithme histogram par rapport à l'algorithme patience, avec le même niveau de détail as description originale de Bram Cohen de l'algorithme patience ?

(Si c'est juste une question de performances d'implémentation sans cas qui produira des résultats différents, pourquoi n'a-t-il pas été simplement implémenté en tant que nouveau backend pour patience?)

50
Stuart P. Bentley

Cette stratégie d'histogramme a été introduite dans git 1.7.7 (sept 2011) , avec la description suivante (comme mentionné par l'OP)

"git diff" A appris une option "--histogram" Pour utiliser une machine de génération de diff différente volée à jgit , ce qui pourrait donner de meilleures performances.

JGit inclut src/org/Eclipse/jgit/diff/HistogramDiff.Java et tst/org/Eclipse/jgit/diff/HistogramDiffTest.Java

La description y est assez complète:

HistogramDiff

Une forme étendue de l'algorithme de diff de patience de Bram Cohen.

Cette implémentation a été dérivée en utilisant les 4 règles décrites dans le blog de Bram Cohen , puis a été étendue pour prendre en charge les éléments communs à faible occurrence.

L'idée de base de l'algorithme est de créer un histogramme des occurrences pour chaque élément de la séquence A . Chaque élément de la séquence B est ensuite considéré à son tour. Si l'élément existe également dans la séquence A et a un nombre d'occurrences inférieur, les positions sont considérées comme un candidat pour la sous-séquence commune la plus longue (LCS).
Une fois la numérisation de B terminée, le LCS qui a le plus petit nombre d'occurrences est choisi comme point de partage. La région est divisée autour du LCS et l'algorithme est appliqué récursivement aux sections avant et après le LCS.

En sélectionnant toujours une position LCS avec le nombre d'occurrences le plus bas, cet algorithme se comporte exactement comme la différence de patience de Bram Cohen chaque fois qu'un élément commun unique est disponible entre les deux séquences.
Quand aucun élément unique n'existe, l'élément le moins fréquent est choisi à la place
.
Cela offre des différences plus lisibles que le simple recours à l'algorithme standard de Myers O(ND).

Pour empêcher l'algorithme d'avoir un temps d'exécution O(N^2), une limite supérieure du nombre d'éléments uniques dans un compartiment d'histogramme est configurée par #setMaxChainLength(int) .
Si la séquence A a plus que ce nombre d'éléments qui sont hachés dans le même compartiment de hachage, l'algorithme passe la région à #setFallbackAlgorithm(DiffAlgorithm) .
Si aucun algorithme de secours n'est configuré, la région est émise en tant que modification de remplacement.

Pendant le balayage de la séquence B, tout élément de A qui se produit plus de #setMaxChainLength(int) fois n'est jamais pris en compte pour une position de correspondance LCS, même s'il est commun entre les deux séquences. Cela limite le nombre d'emplacements dans la séquence A qui doivent être pris en compte pour trouver le LCS et aide à maintenir une limite de temps de fonctionnement inférieure.

Tant que #setMaxChainLength(int) est une petite constante (telle que 64), l'algorithme s'exécute en O(N * D) temps, où N est la somme des longueurs d'entrée et D est le nombre de modifications dans le EditList résultant.
Si le SequenceComparator fourni a une bonne fonction de hachage, cette implémentation surpasse généralement MyersDiff , même si son le temps de fonctionnement théorique est le même.

Cette implémentation a une limitation interne qui l'empêche de gérer des séquences avec plus de 268 435 456 (2 ^ 28) éléments


Notez que ce type d'algo était déjà utilisé pour pack_check, en 2006 (git 1.3) , pour git-verify-pack -v. C'était réutilisé pour index-pack dans git 1.7.7


Commit 8c912ee a effectivement introduit --histogram Dans diff:

L'algorithme HistogramDiff de Port JGit sur C. Les nombres bruts (TODO) montrent qu'il est plus rapide que son cousin --patience, Ainsi que l'algorithme Meyers par défaut.

L'implémentation a été retravaillée pour utiliser des structures et des pointeurs, au lieu de masques de bit, supprimant ainsi la limite de ligne 2^28 De JGit .

Nous utilisons également l'implémentation par défaut de la table de hachage de xdiff (xdl_hash_bits() avec XDL_HASHLONG()) pour plus de commodité.

commit 8555123 (git 1.7.10, avril 2012) ajouté:

8c912ee (enseigner --histogram À diff, 2011-07-12) a affirmé que la différence d'histogramme était plus rapide que Myers et la patience.

Nous avons depuis incorporé un cadre de test de performances, alors ajoutez un test qui compare les différentes tâches de diff effectuées dans une charge de travail réelle "log -p".
Cela montre en effet que l'histogramme diffère légèrement Myers, tandis que la patience est beaucoup plus lente que les autres.

Enfin, commit 07ab4de (git 1.8.2, mars 2013) add

config: Introduire la variable diff.algorithm

Certains utilisateurs ou projets préfèrent des algorithmes différents aux autres, par exemple patience envers les miens ou similaires.
Cependant, il n'est pas pratique de spécifier l'argument approprié chaque fois que diff doit être utilisé. De plus, la création d'un alias ne fonctionne pas bien avec d'autres outils basés sur diff (git-show Par exemple).

Par conséquent, une variable de configuration capable de définir un algorithme spécifique est nécessaire.
Pour l'instant, ces quatre valeurs sont acceptées:

  • 'myers' (ce qui a le même effet que de ne pas définir du tout la variable de configuration),
  • 'minimal',
  • 'patience' et
  • 'histogram'.

Commit 07924d4 ajouté simultanément l'option de ligne de commande --diff-algorithm.
Comme l'OP Stuart P. Bentley mentionne dans les commentaires :

vous pouvez configurer Git pour utiliser l'histogramme par défaut avec:

git config --global diff.algorithm histogram

Mise à jour: Git 2.12 (Q1 2017) supprimera le "hachage rapide" qui avait des problèmes de performances désastreux dans certains cas d'angle.

Voir commit 1f7c926 (01 décembre 2016) par Jeff King (peff)(Fusionné par Junio ​​C Hamano - gitster - in commit 731490b , 19 décembre 2016)

xdiff: drop XDL_FAST_HASH

Le code xdiff hache chaque ligne des deux côtés d'un diff, puis compare ces hachages pour trouver des doublons . Les performances globales dépendent à la fois de la vitesse à laquelle nous pouvons calculer les hachages, mais également du nombre de collisions de hachage que nous voyons.

L'idée de XDL_FAST_HASH Est d'accélérer le calcul du hachage.
Mais les hachages générés ont un comportement de collision pire. Cela signifie que dans certains cas, il accélère les différences (l'exécution de "git log -p" Sur git.git S'améliore de ~8% Avec lui), mais dans d'autres, cela peut ralentir les choses. n cas pathologique a vu un ralentissement de 100x .

Il peut y avoir une meilleure fonction de hachage qui couvre les deux propriétés, mais en attendant, nous sommes mieux avec le hachage d'origine. C'est un peu plus lent dans le cas commun, mais il a moins de cas pathologiques surprenants.


Remarque: "git diff --histogram" Avait un mauvais modèle d'utilisation de la mémoire, qui a été réorganisé pour réduire l'utilisation maximale, avec Git 2.19 (Q3 2018).

Voir commit 79cb2eb , commit 64c4e8b , commit c671d4b , commit 2820985 (19 juil 2018) par Stefan Beller (stefanbeller) .
(Fusionné par Junio ​​C Hamano - gitster - in commit 57fbd8e , 15 août 2018)

xdiff/xhistogram: Déplacez l'allocation d'index dans find_lcs

Cela corrige un problème de mémoire lors de la répétition d'un lot, qui peut être reproduit comme

seq 1   100000 >one
seq 1 4 100000 >two
git diff --no-index --histogram one two

Avant ce correctif, histogram_diff S'appellerait de façon récursive avant d'appeler free_index, Ce qui signifierait qu'une grande quantité de mémoire est allouée pendant la récursivité et libérée uniquement par la suite.

En déplaçant l'allocation de mémoire (et son appel gratuit) dans find_lcs, La mémoire est libérée avant de récurser, de sorte que la mémoire est réutilisée à l'étape suivante de la récursivité au lieu d'utiliser une nouvelle mémoire.

Cela ne concerne que la pression de la mémoire, pas la complexité du temps d'exécution, qui est également terrible pour le cas d'angle décrit ci-dessus.

57
VonC