diff mercurial/bdiff.c @ 29014:f1ca249696ed stable

bdiff: balance recursion to avoid quadratic behavior (issue4704) For highly structured files like JSON or XML dumps with large numbers of duplicate lines (eg braces) and isolated matching lines, bdiff could find large numbers of equally good spans. Because it prefers earlier matches, this would result in pathologically unbalance recursion that resulted in quadratic performance. This patch makes it prefer matches closer to the middle that tend to balance recursion. This change improves the speed of a pathological test case from 1100s to 9s. Included is a smaller test that has a roughly 50x safety margin on the performance it accepts. It's likely to fail on pure builds because difflib also has a recursion-balancing problem.
author Matt Mackall <mpm@selenic.com>
date Thu, 21 Apr 2016 22:04:11 -0500
parents 9a8363d23419
children 87d4a6c5567e
line wrap: on
line diff
--- a/mercurial/bdiff.c	Thu Apr 21 21:05:26 2016 -0500
+++ b/mercurial/bdiff.c	Thu Apr 21 22:04:11 2016 -0500
@@ -148,7 +148,7 @@
 static int longest_match(struct line *a, struct line *b, struct pos *pos,
 			 int a1, int a2, int b1, int b2, int *omi, int *omj)
 {
-	int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k;
+	int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k, half = (a1 + a2) / 2;
 
 	for (i = a1; i < a2; i++) {
 		/* skip all lines in b after the current block */
@@ -165,8 +165,9 @@
 			pos[j].pos = i;
 			pos[j].len = k;
 
-			/* best match so far? */
-			if (k > mk || (k == mk && i <= mi)) {
+			/* best match so far? we prefer matches closer
+			   to the middle to balance recursion */
+			if (k > mk || (k == mk && (i <= mi || i < half))) {
 				mi = i;
 				mj = j;
 				mk = k;