* git-revert is a memory hog @ 2008-01-27 17:27 Adrian Bunk 2008-01-27 17:38 ` Shawn O. Pearce 2008-01-28 5:59 ` Jeff King 0 siblings, 2 replies; 24+ messages in thread From: Adrian Bunk @ 2008-01-27 17:27 UTC (permalink / raw) To: git I'm not sure whether this is already known, but when recently working for some time from a computer with "only" 512 MB RAM I ran into the huge memory usage of git-revert when it tries to revert old commits. Example (in Linus' kernel tree with git 1.5.3.8): <-- snip --> $ git-revert d19fbe8a7 Auto-merged drivers/input/input.c CONFLICT (content): Merge conflict in drivers/input/input.c Auto-merged include/linux/input.h CONFLICT (content): Merge conflict in include/linux/input.h Automatic revert failed. After resolving the conflicts, mark the corrected paths with 'git add <paths>' and commit the result. $ <-- snip --> In top you can see that this took > 800 MB of RAM ! I don't know how easy it would be to implement, but shouldn't git-revert be able to be as fast and less memory consuming as git-show d19fbe8a7 | patch -p1 -R ? cu Adrian -- "Is there not promise of rain?" Ling Tan asked suddenly out of the darkness. There had been need of rain for many days. "Only a promise," Lao Er said. Pearl S. Buck - Dragon Seed ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-27 17:27 git-revert is a memory hog Adrian Bunk @ 2008-01-27 17:38 ` Shawn O. Pearce 2008-01-28 6:01 ` Jeff King 2008-01-28 5:59 ` Jeff King 1 sibling, 1 reply; 24+ messages in thread From: Shawn O. Pearce @ 2008-01-27 17:38 UTC (permalink / raw) To: Adrian Bunk; +Cc: git Adrian Bunk <bunk@kernel.org> wrote: > I'm not sure whether this is already known, but when recently working > for some time from a computer with "only" 512 MB RAM I ran into the huge > memory usage of git-revert when it tries to revert old commits. > > Example (in Linus' kernel tree with git 1.5.3.8): > > $ git-revert d19fbe8a7 ... > In top you can see that this took > 800 MB of RAM ! > > I don't know how easy it would be to implement, but shouldn't git-revert > be able to be as fast and less memory consuming as > git-show d19fbe8a7 | patch -p1 -R Its more like: git-diff-tree -M d19fbe8a7^ d19fbe8a7 | git-apply -R --index In other words its doing rename detection. Its possible that the rename detector fired for added/removed paths and it took some significant amount of memory to figure out what was renamed. Maybe we hung onto stuff for too long, but it has to do rename detection and that takes memory to store the matrix and file contents. Once memory is allocated by git we don't give it back to the kernel, even if we free'd it internally. If you really are tight on memory and have to do a revert you can use the above, but minus the -M, to setup the change, but you may run into trouble if renames were involved. -- Shawn. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-27 17:38 ` Shawn O. Pearce @ 2008-01-28 6:01 ` Jeff King 0 siblings, 0 replies; 24+ messages in thread From: Jeff King @ 2008-01-28 6:01 UTC (permalink / raw) To: Shawn O. Pearce; +Cc: Adrian Bunk, git On Sun, Jan 27, 2008 at 12:38:08PM -0500, Shawn O. Pearce wrote: > > git-show d19fbe8a7 | patch -p1 -R > > Its more like: > > git-diff-tree -M d19fbe8a7^ d19fbe8a7 | git-apply -R --index > > In other words its doing rename detection. Its possible that the > rename detector fired for added/removed paths and it took some > significant amount of memory to figure out what was renamed. It's doubtful. There are only two files in that diff, and neither is a candidate for rename detection. -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-27 17:27 git-revert is a memory hog Adrian Bunk 2008-01-27 17:38 ` Shawn O. Pearce @ 2008-01-28 5:59 ` Jeff King 2008-01-29 21:51 ` Linus Torvalds 1 sibling, 1 reply; 24+ messages in thread From: Jeff King @ 2008-01-28 5:59 UTC (permalink / raw) To: Adrian Bunk; +Cc: git On Sun, Jan 27, 2008 at 07:27:48PM +0200, Adrian Bunk wrote: > <-- snip --> > > $ git-revert d19fbe8a7 > Auto-merged drivers/input/input.c > CONFLICT (content): Merge conflict in drivers/input/input.c > Auto-merged include/linux/input.h > CONFLICT (content): Merge conflict in include/linux/input.h > Automatic revert failed. After resolving the conflicts, > mark the corrected paths with 'git add <paths>' and commit the result. > $ > > <-- snip --> > > In top you can see that this took > 800 MB of RAM ! I tried to reproduce this, but my peak heap allocation was only around 20MB. Is your repository fully packed? Not packed at all? Can you use valgrind/massif to figure out where the memory is going? > I don't know how easy it would be to implement, but shouldn't git-revert > be able to be as fast and less memory consuming as > git-show d19fbe8a7 | patch -p1 -R In your case, the patch doesn't apply cleanly, so we end up doing a 3-way merge (in my tests, it is git-merge-recursive which ends up taking up the memory). -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-28 5:59 ` Jeff King @ 2008-01-29 21:51 ` Linus Torvalds 2008-01-29 22:15 ` Junio C Hamano 2008-01-29 22:20 ` Jeff King 0 siblings, 2 replies; 24+ messages in thread From: Linus Torvalds @ 2008-01-29 21:51 UTC (permalink / raw) To: Jeff King; +Cc: Adrian Bunk, git On Mon, 28 Jan 2008, Jeff King wrote: > > I tried to reproduce this, but my peak heap allocation was only around > 20MB. Is your repository fully packed? Not packed at all? Can you use > valgrind/massif to figure out where the memory is going? I definitely can reproduce it, it's horrid. This is from "top" fairly late in the game, but with the thing not even done yet. Current git, pretty much fully (and fairly aggressively) packed current kernel repo, and using "diff.renamelmit=0". 4751 torvalds 20 0 852m 446m 47m R 72 22.4 2:46.58 git-merge-recur It finally finished with time reporting: 208.15user 3.50system 4:01.50elapsed 87%CPU (0avgtext+0avgdata 0maxresident)k 238736inputs+4544outputs (8261major+280971minor)pagefaults 0swaps where those 280971 minor page faults are what largely indicates how much memory it used (the technical term for that number is "metric buttload of memory"). But I'm in Melbourne right now on my laptop,and probably won't be able to debug this much. > In your case, the patch doesn't apply cleanly, so we end up doing a > 3-way merge (in my tests, it is git-merge-recursive which ends up taking > up the memory). It is indeed git-merge-recursive. It just shouldn't take that much memory. Linus ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 21:51 ` Linus Torvalds @ 2008-01-29 22:15 ` Junio C Hamano 2008-01-29 22:20 ` Jeff King 1 sibling, 0 replies; 24+ messages in thread From: Junio C Hamano @ 2008-01-29 22:15 UTC (permalink / raw) To: Linus Torvalds; +Cc: Jeff King, Adrian Bunk, git Linus Torvalds <torvalds@linux-foundation.org> writes: > On Mon, 28 Jan 2008, Jeff King wrote: >> >> I tried to reproduce this, but my peak heap allocation was only around >> 20MB. Is your repository fully packed? Not packed at all? Can you use >> valgrind/massif to figure out where the memory is going? > > I definitely can reproduce it, it's horrid. > > This is from "top" fairly late in the game, but with the thing not even > done yet. Current git, pretty much fully (and fairly aggressively) packed > current kernel repo, and using "diff.renamelmit=0". > > 4751 torvalds 20 0 852m 446m 47m R 72 22.4 2:46.58 git-merge-recur > > It finally finished with time reporting: > > 208.15user 3.50system 4:01.50elapsed 87%CPU (0avgtext+0avgdata 0maxresident)k > 238736inputs+4544outputs (8261major+280971minor)pagefaults 0swaps > > where those 280971 minor page faults are what largely indicates how much > memory it used (the technical term for that number is "metric buttload of > memory"). > > But I'm in Melbourne right now on my laptop,and probably won't be able to > debug this much. > >> In your case, the patch doesn't apply cleanly, so we end up doing a >> 3-way merge (in my tests, it is git-merge-recursive which ends up taking >> up the memory). > > It is indeed git-merge-recursive. It just shouldn't take that much memory. Hmmmmm. Obviously this depends on where you start your revert from, but that is not what I am getting. : gitster linux-2.6/test; git reset --hard HEAD is now at 0ba6c33... Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6.25 : gitster linux-2.6/test; /usr/bin/time git-revert d19fbe8a7 Auto-merged drivers/input/input.c CONFLICT (content): Merge conflict in drivers/input/input.c Auto-merged include/linux/input.h CONFLICT (content): Merge conflict in include/linux/input.h Automatic revert failed. After resolving the conflicts, mark the corrected paths with 'git add <paths>' or 'git rm <paths>' and commit the result. Command exited with non-zero status 1 1.08user 0.06system 0:01.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+19867minor)pagefaults 0swaps Now, a possible alternative (that produces an identical result) is not much better, because it ends up using merge-recursive as its core. : gitster linux-2.6/test; git reset --hard HEAD is now at 0ba6c33... Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-2.6.25 : gitster linux-2.6/test; rm -fr .dotest : gitster linux-2.6/test; /usr/bin/time sh -c 'git format-patch -R --binary --stdout -1 d19fbe8a7 | git am -3' Applying Input: prepare to sysfs integration error: patch failed: drivers/input/input.c:27 error: drivers/input/input.c: patch does not apply error: patch failed: include/linux/input.h:12 error: include/linux/input.h: patch does not apply Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... Auto-merged drivers/input/input.c CONFLICT (content): Merge conflict in drivers/input/input.c Auto-merged include/linux/input.h CONFLICT (content): Merge conflict in include/linux/input.h Failed to merge in the changes. Patch failed at 0001. When you have resolved this problem run "git-am -3 --resolved". If you would prefer to skip this patch, instead run "git-am -3 --skip". Command exited with non-zero status 1 0.52user 0.25system 0:00.75elapsed 103%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+41208minor)pagefaults 0swaps ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 21:51 ` Linus Torvalds 2008-01-29 22:15 ` Junio C Hamano @ 2008-01-29 22:20 ` Jeff King 2008-01-29 22:30 ` Jeff King 2008-01-29 22:36 ` Junio C Hamano 1 sibling, 2 replies; 24+ messages in thread From: Jeff King @ 2008-01-29 22:20 UTC (permalink / raw) To: Linus Torvalds; +Cc: Adrian Bunk, git On Wed, Jan 30, 2008 at 08:51:09AM +1100, Linus Torvalds wrote: > I definitely can reproduce it, it's horrid. > > This is from "top" fairly late in the game, but with the thing not even > done yet. Current git, pretty much fully (and fairly aggressively) packed > current kernel repo, and using "diff.renamelmit=0". Hrm, setting diff.renamelimit to 0 lets me reproduce (I thought I tried it before, but clearly not...). The culprit seems to be diffcore-rename.c:476: mx = xmalloc(sizeof(*mx) * num_create * num_src); Where that ends up allocating about 450M. I think this is exactly the sort of case that renamelimit was introduced to address. -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:20 ` Jeff King @ 2008-01-29 22:30 ` Jeff King 2008-01-29 22:36 ` Junio C Hamano 1 sibling, 0 replies; 24+ messages in thread From: Jeff King @ 2008-01-29 22:30 UTC (permalink / raw) To: Linus Torvalds; +Cc: Adrian Bunk, git On Tue, Jan 29, 2008 at 05:20:07PM -0500, Jeff King wrote: > The culprit seems to be diffcore-rename.c:476: > > mx = xmalloc(sizeof(*mx) * num_create * num_src); > > Where that ends up allocating about 450M. I think this is exactly the > sort of case that renamelimit was introduced to address. BTW, a much easier way to see the problem is with: git diff -M -l0 d19fbe8a76 It's just a _really_ old commit, and the rename detection has to work on a large number of files. -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:20 ` Jeff King 2008-01-29 22:30 ` Jeff King @ 2008-01-29 22:36 ` Junio C Hamano 2008-01-29 22:45 ` Jeff King ` (3 more replies) 1 sibling, 4 replies; 24+ messages in thread From: Junio C Hamano @ 2008-01-29 22:36 UTC (permalink / raw) To: Jeff King; +Cc: Linus Torvalds, Adrian Bunk, git Jeff King <peff@peff.net> writes: > On Wed, Jan 30, 2008 at 08:51:09AM +1100, Linus Torvalds wrote: > >> I definitely can reproduce it, it's horrid. >> >> This is from "top" fairly late in the game, but with the thing not even >> done yet. Current git, pretty much fully (and fairly aggressively) packed >> current kernel repo, and using "diff.renamelmit=0". > > Hrm, setting diff.renamelimit to 0 lets me reproduce (I thought I tried > it before, but clearly not...). Hmph. But I wonder why this part does not trigger, even when you have renamelimit set to 0. /* * This basically does a test for the rename matrix not * growing larger than a "rename_limit" square matrix, ie: * * rename_dst_nr * rename_src_nr > rename_limit * rename_limit * * but handles the potential overflow case specially (and we * assume at least 32-bit integers) */ if (rename_limit <= 0 || rename_limit > 32767) rename_limit = 32767; if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) goto cleanup; if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) goto cleanup; I wonder if the second one for the overflow avoidance should be using || instead of &&, though. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:36 ` Junio C Hamano @ 2008-01-29 22:45 ` Jeff King 2008-01-29 22:51 ` Jeff King 2008-01-29 22:49 ` Linus Torvalds ` (2 subsequent siblings) 3 siblings, 1 reply; 24+ messages in thread From: Jeff King @ 2008-01-29 22:45 UTC (permalink / raw) To: Junio C Hamano; +Cc: Linus Torvalds, Adrian Bunk, git On Tue, Jan 29, 2008 at 02:36:24PM -0800, Junio C Hamano wrote: > Hmph. But I wonder why this part does not trigger, even when > you have renamelimit set to 0. > [...] > if (rename_limit <= 0 || rename_limit > 32767) > rename_limit = 32767; It does trigger; we set the limit to the obscenely high 32767. My matrix was something like 8000x3500. > if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) > goto cleanup; > if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) > goto cleanup; > > I wonder if the second one for the overflow avoidance should be > using || instead of &&, though. Hrm, yes, I think it can still overflow. (e.g., a 2 by 2^32-1 situation). But changing it to || isn't right, either; you would disallow 1 by 101, which is quite do-able (and the normal case for -C -C, I would think). -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:45 ` Jeff King @ 2008-01-29 22:51 ` Jeff King 0 siblings, 0 replies; 24+ messages in thread From: Jeff King @ 2008-01-29 22:51 UTC (permalink / raw) To: Junio C Hamano; +Cc: Linus Torvalds, Adrian Bunk, git On Tue, Jan 29, 2008 at 05:45:58PM -0500, Jeff King wrote: > > if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) > > goto cleanup; > > Hrm, yes, I think it can still overflow. (e.g., a 2 by 2^32-1 > situation). But changing it to || isn't right, either; you would I think the correct solution is just: /* check for overflow of square */ if (rename_dst_nr > ULONG_MAX / rename_src_nr) goto cleanup; -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:36 ` Junio C Hamano 2008-01-29 22:45 ` Jeff King @ 2008-01-29 22:49 ` Linus Torvalds 2008-01-29 22:54 ` Jeff King 2008-01-29 22:53 ` Junio C Hamano 2008-01-29 23:19 ` Junio C Hamano 3 siblings, 1 reply; 24+ messages in thread From: Linus Torvalds @ 2008-01-29 22:49 UTC (permalink / raw) To: Junio C Hamano; +Cc: Jeff King, Adrian Bunk, git On Tue, 29 Jan 2008, Junio C Hamano wrote: > > I wonder if the second one for the overflow avoidance should be > using || instead of &&, though. No, we want to be able to handle the case where there is (for example) just one removed file, but lots of new ones. That's not expensive at all. So we don't want to require that *both* the counts for removed and new files are low, we really want to check that we don't have too many combinations together. But the if (rename_limit <= 0 || rename_limit > 32767) rename_limit = 32767; which is there purely to avoid overflow in 32-bit multiplication should probably be changed to be more reasonable. We'll never want to try to do a matrix that is really 32k * 32k in size, even if we can calculate its size ;) So maybe we should just make that hard limit more reasonable. 100x100 was too small, but a 1000x1000 matrix might be acceptable. Or, better yet (which was what I was hoping for originally), we'd just make the inexact rename detection be linear-size/time rather than O(m*n). But those patches never really came together, so we do need to limit it more aggressively. Linus ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:49 ` Linus Torvalds @ 2008-01-29 22:54 ` Jeff King 0 siblings, 0 replies; 24+ messages in thread From: Jeff King @ 2008-01-29 22:54 UTC (permalink / raw) To: Linus Torvalds; +Cc: Junio C Hamano, Adrian Bunk, git On Wed, Jan 30, 2008 at 09:49:44AM +1100, Linus Torvalds wrote: > But the > > if (rename_limit <= 0 || rename_limit > 32767) > rename_limit = 32767; > > which is there purely to avoid overflow in 32-bit multiplication should Ah, right, that first conditional handles the overflow. Then what is the second one doing? If they are both larger than the rename limit, then won't there square by definition be larger than the square of the rename limit? I.e., can't we just get rid of the second conditional? > Or, better yet (which was what I was hoping for originally), we'd just > make the inexact rename detection be linear-size/time rather than O(m*n). > But those patches never really came together, so we do need to limit it > more aggressively. I had trouble getting the memory usage to a reasonable level. The hash tables were just getting enormous. -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:36 ` Junio C Hamano 2008-01-29 22:45 ` Jeff King 2008-01-29 22:49 ` Linus Torvalds @ 2008-01-29 22:53 ` Junio C Hamano 2008-01-29 22:57 ` Jeff King 2008-01-29 23:19 ` Junio C Hamano 3 siblings, 1 reply; 24+ messages in thread From: Junio C Hamano @ 2008-01-29 22:53 UTC (permalink / raw) To: Jeff King; +Cc: Linus Torvalds, Adrian Bunk, git Junio C Hamano <gitster@pobox.com> writes: > Jeff King <peff@peff.net> writes: > >> Hrm, setting diff.renamelimit to 0 lets me reproduce (I thought I tried >> it before, but clearly not...). > > Hmph. But I wonder why this part does not trigger, even when > you have renamelimit set to 0. > > /* > * This basically does a test for the rename matrix not > * growing larger than a "rename_limit" square matrix, ie: > * > * rename_dst_nr * rename_src_nr > rename_limit * rename_limit > * > * but handles the potential overflow case specially (and we > * assume at least 32-bit integers) > */ > if (rename_limit <= 0 || rename_limit > 32767) > rename_limit = 32767; > if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) > goto cleanup; > if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) > goto cleanup; > > I wonder if the second one for the overflow avoidance should be > using || instead of &&, though. Reverting d19fbe8a7 means coming up with a 3-way merge between d19fbe8a7^ and master as if d19fbe8a7 is their common ancestor. "git diff --name-status d19fbe8a7 d19fbe8a7^" shows only two paths changed. "git diff --name-status d19fbe8a7 master" shows 8558 new paths and 3756 deleted paths, which makes 32m paths pairs, that is still lower than 32767 squared. If your int is 64-bit, struct diff_score which is 4-int is 16-byte long. 32m * 16 = 501,801,600. That seems to match your 450MB observation well. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:53 ` Junio C Hamano @ 2008-01-29 22:57 ` Jeff King 0 siblings, 0 replies; 24+ messages in thread From: Jeff King @ 2008-01-29 22:57 UTC (permalink / raw) To: Junio C Hamano; +Cc: Linus Torvalds, Adrian Bunk, git On Tue, Jan 29, 2008 at 02:53:12PM -0800, Junio C Hamano wrote: > If your int is 64-bit, struct diff_score which is 4-int is > 16-byte long. 32m * 16 = 501,801,600. That seems to match your > 450MB observation well. Yes, except for s/64-bit/32-bit/. -Peff ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 22:36 ` Junio C Hamano ` (2 preceding siblings ...) 2008-01-29 22:53 ` Junio C Hamano @ 2008-01-29 23:19 ` Junio C Hamano 2008-01-29 23:50 ` Junio C Hamano 2008-01-30 4:40 ` [PATCH] Optimize rename detection for a huge diff Junio C Hamano 3 siblings, 2 replies; 24+ messages in thread From: Junio C Hamano @ 2008-01-29 23:19 UTC (permalink / raw) To: Linus Torvalds, Jeff King; +Cc: Adrian Bunk, git Junio C Hamano <gitster@pobox.com> writes: > Jeff King <peff@peff.net> writes: > >> On Wed, Jan 30, 2008 at 08:51:09AM +1100, Linus Torvalds wrote: >> >>> I definitely can reproduce it, it's horrid. >>> >>> This is from "top" fairly late in the game, but with the thing not even >>> done yet. Current git, pretty much fully (and fairly aggressively) packed >>> current kernel repo, and using "diff.renamelmit=0". >> >> Hrm, setting diff.renamelimit to 0 lets me reproduce (I thought I tried >> it before, but clearly not...). How about this fairly obvious patch? We used to record, for N=src deleted paths and M=dst created paths, (N x M) "struct diff_score" that records how similar each of the pair is, and picked the <src,dst> pairs that gives the best match first, and then went on to worse matches. This sorting is so that two destinations are both found to be similar to a single source, we can process the more similar one first, and when processing the second one, it can notice "Ah, the source I was planning to say I am a copy of is already taken by somebody else" and continue on to match himself with another source with a lessor match (this matters to a change introduced between 1.5.3.X series and 1.5.4-rc, that lets the code to favor unused matches first and then falls back to using already used matches). This instead allocates and keeps only M records in core. For each dst, we compute similarlity with all sources (so the number of similarity estimate computations we do is still N x M), but we keep the best src for each dst. This is essentially to save memory drastically by giving up to come up with better pairing. I guess we could keep a handful best candidates per dst, instead of just one, to further improve on this approach, and such a change should be fairly straightforward. --- diffcore-rename.c | 46 +++++++++++++++++++++++----------------------- 1 files changed, 23 insertions(+), 23 deletions(-) diff --git a/diffcore-rename.c b/diffcore-rename.c index 3d37725..7e8fdcd 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -473,42 +473,42 @@ void diffcore_rename(struct diff_options *options) if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - mx = xmalloc(sizeof(*mx) * num_create * num_src); + + mx = xmalloc(sizeof(*mx) * num_create); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { - int base = dst_cnt * num_src; struct diff_filespec *two = rename_dst[i].two; + struct diff_score *m; + if (rename_dst[i].pair) continue; /* dealt with exact match already. */ + + m = &mx[dst_cnt]; + m->dst = -1; for (j = 0; j < rename_src_nr; j++) { struct diff_filespec *one = rename_src[j].one; - struct diff_score *m = &mx[base+j]; - m->src = j; - m->dst = i; - m->score = estimate_similarity(one, two, - minimum_score); - m->name_score = basename_same(one, two); + int score, name_score; + score = estimate_similarity(one, two, + minimum_score); + name_score = basename_same(one, two); + if (m->dst < 0 || + score > m->score || + (score == m->score && + name_score > m->name_score)) { + m->score = score; + m->name_score = name_score; + m->src = j; + m->dst = i; + } diff_free_filespec_blob(one); } /* We do not need the text anymore */ diff_free_filespec_blob(two); dst_cnt++; } + /* cost matrix sorted by most to least similar pair */ - qsort(mx, num_create * num_src, sizeof(*mx), score_compare); - for (i = 0; i < num_create * num_src; i++) { - struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; - struct diff_filespec *src; - if (dst->pair) - continue; /* already done, either exact or fuzzy. */ - if (mx[i].score < minimum_score) - break; /* there is no more usable pair. */ - src = rename_src[mx[i].src].one; - if (src->rename_used) - continue; - record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); - rename_count++; - } - for (i = 0; i < num_create * num_src; i++) { + qsort(mx, num_create, sizeof(*mx), score_compare); + for (i = 0; i < num_create; i++) { struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: git-revert is a memory hog 2008-01-29 23:19 ` Junio C Hamano @ 2008-01-29 23:50 ` Junio C Hamano 2008-01-30 4:40 ` [PATCH] Optimize rename detection for a huge diff Junio C Hamano 1 sibling, 0 replies; 24+ messages in thread From: Junio C Hamano @ 2008-01-29 23:50 UTC (permalink / raw) To: Linus Torvalds; +Cc: Jeff King, Adrian Bunk, git Junio C Hamano <gitster@pobox.com> writes: > This instead allocates and keeps only M records in core. For > each dst, we compute similarlity with all sources (so the number > of similarity estimate computations we do is still N x M), but > we keep the best src for each dst. This is essentially to save > memory drastically by giving up to come up with better pairing. > > I guess we could keep a handful best candidates per dst, instead > of just one, to further improve on this approach, and such a > change should be fairly straightforward. An obvious side note to this patch is that if we are going to limit us to only 1 source candidate per destination, we do not even have to allocate. We can just do similarity one-by-one for each destination, and pair up with the best source as we go. I did not code it that way, primarily because that would permanently close the door to extend it back to keep multiple candidates per dst, so that later ones that gets processed can notice what happened to earlier ones. ^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH] Optimize rename detection for a huge diff 2008-01-29 23:19 ` Junio C Hamano 2008-01-29 23:50 ` Junio C Hamano @ 2008-01-30 4:40 ` Junio C Hamano 2008-01-30 6:57 ` Luke Lu 2008-02-13 9:53 ` Junio C Hamano 1 sibling, 2 replies; 24+ messages in thread From: Junio C Hamano @ 2008-01-30 4:40 UTC (permalink / raw) To: Linus Torvalds; +Cc: Jeff King, Adrian Bunk, git When there are N deleted paths and M created paths, we used to allocate (N x M) "struct diff_score" that record how similar each of the pair is, and picked the <src,dst> pair that gives the best match first, and then went on to process worse matches. This sorting is done so that when two new files in the postimage that are similar to the same file deleted from the preimage, we can process the more similar one first, and when processing the second one, it can notice "Ah, the source I was planning to say I am a copy of is already taken by somebody else" and continue on to match itself with another file in the preimage with a lessor match. This matters to a change introduced between 1.5.3.X series and 1.5.4-rc, that lets the code to favor unused matches first and then falls back to using already used matches. This instead allocates and keeps only a handful rename source candidates per new files in the postimage. I.e. it makes the memory requirement from O(N x M) to O(M). For each dst, we compute similarlity with all sources (i.e. the number of similarity estimate computations is still O(N x M)), but we keep handful best src candidates for each dst. I've run git diff -l0 -M --name-status v2.6.$i v2.6.$(($i+1)) with and without patch for i=15..23. There is one pair between v2.6.20 and v2.6.21 that gets different matching from this version. R093 include/asm-arm/apm.h include/linux/apm-emulation.h R093 include/asm-mips/apm.h include/linux/apm-emulation.h Without the patch, apm-emulation.h is found to be from asm-arm/arm.h; with the patch, it is found to be from asm-mips/apm.h. But the difference between the ARM and MIPS versions is quite small, so I do not think we need to even call this an regression: diff --git a/v2.6.20:include/asm-arm/apm.h b/v2.6.20:include/asm-mips/apm.h index d09113b..4b99ffc 100644 --- a/v2.6.20:include/asm-arm/apm.h +++ b/v2.6.20:include/asm-mips/apm.h @@ -10,8 +10,8 @@ * * */ -#ifndef ARM_ASM_SA1100_APM_H -#define ARM_ASM_SA1100_APM_H +#ifndef MIPS_ASM_SA1100_APM_H +#define MIPS_ASM_SA1100_APM_H #include <linux/apm_bios.h> And the renamed diff is like this. diff --git a/v2.6.20:include/asm-arm/apm.h b/v2.6.21:include/linux/apm-emulation.h index d09113b..e6d8003 100644 --- a/v2.6.20:include/asm-arm/apm.h +++ b/v2.6.21:include/linux/apm-emulation.h @@ -7,11 +7,9 @@ * based on arch/arm/kernel/apm.c * factor out the information needed by architectures to provide * apm status - * - * */ -#ifndef ARM_ASM_SA1100_APM_H -#define ARM_ASM_SA1100_APM_H +#ifndef __LINUX_APM_EMULATION_H +#define __LINUX_APM_EMULATION_H #include <linux/apm_bios.h> @@ -61,4 +59,4 @@ extern void (*apm_get_power_status)(struct apm_power_info *); */ void apm_queue_event(apm_event_t event); -#endif +#endif /* __LINUX_APM_EMULATION_H */ By looking at the above two diffs, I would say picking between ARM and MIPS is equally good and the behaviour difference should not matter in practice. Rename-detecting diff without limit between v2.6.20 and v2.6.24 produces 991 renames and 57 copies, with or without the patch (the resulting pairs are somewhat deferent, but I've looked at a few and the differences looked fairly small, like the above header files). The output from /usr/bin/time are: (without patch) 28.84user 0.33system 0:29.35elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (19major+90874minor)pagefaults 0swaps (with patch) 24.81user 0.12system 0:24.93elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+36553minor)pagefaults 0swaps Signed-off-by: Junio C Hamano <gitster@pobox.com> --- Junio C Hamano <gitster@pobox.com> writes: > How about this fairly obvious patch? This replaces the previous one by implementing "keep the best N candidates per each destination" as I hinted in my earlier message. I suspect that we may want to optimize the O(N x M) part by stopping after finding enough number of good enough candidates for a given destination. diffcore-rename.c | 77 +++++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 57 insertions(+), 20 deletions(-) diff --git a/diffcore-rename.c b/diffcore-rename.c index 3d37725..90d06f0 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -223,6 +223,12 @@ static int score_compare(const void *a_, const void *b_) { const struct diff_score *a = a_, *b = b_; + /* sink the unused ones to the bottom */ + if (a->dst < 0) + return (0 <= b->dst); + else if (b->dst < 0) + return -1; + if (a->score == b->score) return b->name_score - a->name_score; @@ -387,6 +393,22 @@ static int find_exact_renames(void) return i; } +#define NUM_CANDIDATE_PER_DST 8 +static void record_if_better(struct diff_score m[], struct diff_score *o) +{ + int i, worst; + + /* find the worst one */ + worst = 0; + for (i = 1; i < NUM_CANDIDATE_PER_DST; i++) + if (score_compare(&m[i], &m[worst]) > 0) + worst = i; + + /* is it better than the worst one? */ + if (score_compare(&m[worst], o) > 0) + m[worst] = *o; +} + void diffcore_rename(struct diff_options *options) { int detect_rename = options->detect_rename; @@ -473,50 +495,65 @@ void diffcore_rename(struct diff_options *options) if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - mx = xmalloc(sizeof(*mx) * num_create * num_src); + mx = xcalloc(num_create * NUM_CANDIDATE_PER_DST, sizeof(*mx)); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { - int base = dst_cnt * num_src; struct diff_filespec *two = rename_dst[i].two; + struct diff_score *m; + if (rename_dst[i].pair) continue; /* dealt with exact match already. */ + + m = &mx[dst_cnt * NUM_CANDIDATE_PER_DST]; + for (j = 0; j < NUM_CANDIDATE_PER_DST; j++) + m[j].dst = -1; + for (j = 0; j < rename_src_nr; j++) { struct diff_filespec *one = rename_src[j].one; - struct diff_score *m = &mx[base+j]; - m->src = j; - m->dst = i; - m->score = estimate_similarity(one, two, - minimum_score); - m->name_score = basename_same(one, two); + struct diff_score this_src; + this_src.score = estimate_similarity(one, two, + minimum_score); + this_src.name_score = basename_same(one, two); + this_src.dst = i; + this_src.src = j; + record_if_better(m, &this_src); diff_free_filespec_blob(one); } /* We do not need the text anymore */ diff_free_filespec_blob(two); dst_cnt++; } + /* cost matrix sorted by most to least similar pair */ - qsort(mx, num_create * num_src, sizeof(*mx), score_compare); - for (i = 0; i < num_create * num_src; i++) { - struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; - struct diff_filespec *src; + qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare); + + for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { + struct diff_rename_dst *dst; + + if ((mx[i].dst < 0) || + (mx[i].score < minimum_score)) + break; /* there is no more usable pair. */ + dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ - if (mx[i].score < minimum_score) - break; /* there is no more usable pair. */ - src = rename_src[mx[i].src].one; - if (src->rename_used) + if (rename_src[mx[i].src].one->rename_used) continue; record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); rename_count++; } - for (i = 0; i < num_create * num_src; i++) { - struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; + + for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { + struct diff_rename_dst *dst; + + if ((mx[i].dst < 0) || + (mx[i].score < minimum_score)) + break; /* there is no more usable pair. */ + dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ - if (mx[i].score < minimum_score) - break; /* there is no more usable pair. */ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); rename_count++; } + free(mx); cleanup: ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-01-30 4:40 ` [PATCH] Optimize rename detection for a huge diff Junio C Hamano @ 2008-01-30 6:57 ` Luke Lu 2008-01-30 7:24 ` Luke Lu 2008-02-13 9:53 ` Junio C Hamano 1 sibling, 1 reply; 24+ messages in thread From: Luke Lu @ 2008-01-30 6:57 UTC (permalink / raw) To: Junio C Hamano; +Cc: Linus Torvalds, Jeff King, Adrian Bunk, git On Jan 29, 2008, at 8:40 PM, Junio C Hamano wrote: > When there are N deleted paths and M created paths, we used to > allocate (N x M) "struct diff_score" that record how similar > each of the pair is, and picked the <src,dst> pair that gives > the best match first, and then went on to process worse matches. > > This sorting is done so that when two new files in the postimage > that are similar to the same file deleted from the preimage, we > can process the more similar one first, and when processing the > second one, it can notice "Ah, the source I was planning to say > I am a copy of is already taken by somebody else" and continue > on to match itself with another file in the preimage with a > lessor match. This matters to a change introduced between > 1.5.3.X series and 1.5.4-rc, that lets the code to favor unused > matches first and then falls back to using already used > matches. > > This instead allocates and keeps only a handful rename source > candidates per new files in the postimage. I.e. it makes the > memory requirement from O(N x M) to O(M). > > For each dst, we compute similarlity with all sources (i.e. the > number of similarity estimate computations is still O(N x M)), > but we keep handful best src candidates for each dst. I can think of cases where you'll throw away better candidates this way. How about using a priority queue of size max(N, M)? I don't know about the details of the current algorithm but it seems to me that using a naive Rabin Karp fingerprinting approach would not use too much memory: say L is total number of bytes of created files and the fingerprint size S and hash size of 4 bytes. To keep track of M files You only need to keep 8(additional 4 bytes as an index to the file names)*(L/S + M(for filenames)) plus some overhead for the hash table in memory. One pass through D (number of bytes of deleted files) you can get the NxM scores. The score is defined as Wf * Mf + Wt, where Wf is the weight for fingerprinting match and Wt is the weight for title match score; Mf is the fingerprint match score = (number of matching fingerprints)/(number of fingerprints of original (deleted) file). Wf and Wt can be tuned to boost exact basename match. By pushing the scores into a priority queue you'll get the final top (max(N, M) = K) scores in the end. The computation complexity is really O(D+L+(MxN)logK) and memory requirement O(L) You can compute the entire linux source tree renaming (24K files and total 260MB uncompressed) this way using only about 92MB of memory in 18 seconds (limited by hash lookup speed, assuming 15M lookups per second based on my past experience). __Luke ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-01-30 6:57 ` Luke Lu @ 2008-01-30 7:24 ` Luke Lu 0 siblings, 0 replies; 24+ messages in thread From: Luke Lu @ 2008-01-30 7:24 UTC (permalink / raw) To: Luke Lu; +Cc: Junio C Hamano, Linus Torvalds, Jeff King, Adrian Bunk, git On Jan 29, 2008, at 10:57 PM, Luke Lu wrote: > On Jan 29, 2008, at 8:40 PM, Junio C Hamano wrote: >> When there are N deleted paths and M created paths, we used to >> allocate (N x M) "struct diff_score" that record how similar >> each of the pair is, and picked the <src,dst> pair that gives >> the best match first, and then went on to process worse matches. >> >> This sorting is done so that when two new files in the postimage >> that are similar to the same file deleted from the preimage, we >> can process the more similar one first, and when processing the >> second one, it can notice "Ah, the source I was planning to say >> I am a copy of is already taken by somebody else" and continue >> on to match itself with another file in the preimage with a >> lessor match. This matters to a change introduced between >> 1.5.3.X series and 1.5.4-rc, that lets the code to favor unused >> matches first and then falls back to using already used >> matches. >> >> This instead allocates and keeps only a handful rename source >> candidates per new files in the postimage. I.e. it makes the >> memory requirement from O(N x M) to O(M). >> >> For each dst, we compute similarlity with all sources (i.e. the >> number of similarity estimate computations is still O(N x M)), >> but we keep handful best src candidates for each dst. > > I can think of cases where you'll throw away better candidates this > way. How about using a priority queue of size max(N, M)? > > I don't know about the details of the current algorithm but it > seems to me that using a naive Rabin Karp fingerprinting approach > would not use too much memory: say L is total number of bytes of > created files and the fingerprint size S and hash size of 4 bytes. > To keep track of M files You only need to keep 8(additional 4 bytes > as an index to the file names)*(L/S + M(for filenames)) plus some > overhead for the hash table in memory. One pass through D (number > of bytes of deleted files) you can get the NxM scores. The score is > defined as Wf * Mf + Wt, where Wf is the weight for fingerprinting > match and Wt is the weight for title match score; Mf is the > fingerprint match score = (number of matching fingerprints)/(number > of fingerprints of original (deleted) file). Wf and Wt can be tuned > to boost exact basename match. > > By pushing the scores into a priority queue you'll get the final > top (max(N, M) = K) scores in the end. The computation complexity > is really O(D+L+(MxN)logK) and memory requirement O(L) > > You can compute the entire linux source tree renaming (24K files > and total 260MB uncompressed) this way using only about 92MB of > memory in 18 seconds (limited by hash lookup speed, assuming 15M > lookups per second based on my past experience). The estimate is based on fingerprint size of 64 bytes and a 2.4GHz C2D class Intel CPU, YMMV. One can trade off the accuracy for less memory by using larger fingerprint size and vice versa. __Luke ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-01-30 4:40 ` [PATCH] Optimize rename detection for a huge diff Junio C Hamano 2008-01-30 6:57 ` Luke Lu @ 2008-02-13 9:53 ` Junio C Hamano 2008-02-13 10:19 ` David Kastrup 2008-02-14 3:00 ` Junio C Hamano 1 sibling, 2 replies; 24+ messages in thread From: Junio C Hamano @ 2008-02-13 9:53 UTC (permalink / raw) To: Linus Torvalds; +Cc: Jeff King, Adrian Bunk, git Linus Torvalds <torvalds@linux-foundation.org> writes: > On Mon, 28 Jan 2008, Jeff King wrote: >> >> I tried to reproduce this, but my peak heap allocation was only around >> 20MB. Is your repository fully packed? Not packed at all? Can you use >> valgrind/massif to figure out where the memory is going? > > I definitely can reproduce it, it's horrid. > > This is from "top" fairly late in the game, but with the thing not even > done yet. Current git, pretty much fully (and fairly aggressively) packed > current kernel repo, and using "diff.renamelmit=0". > > 4751 torvalds 20 0 852m 446m 47m R 72 22.4 2:46.58 git-merge-recur > > It finally finished with time reporting: > > 208.15user 3.50system 4:01.50elapsed 87%CPU (0avgtext+0avgdata 0maxresident)k > 238736inputs+4544outputs (8261major+280971minor)pagefaults 0swaps > > where those 280971 minor page faults are what largely indicates how much > memory it used (the technical term for that number is "metric buttload of > memory"). With a bit of tweak, now I am getting these numbers to the rename detection that used to spend 800MB (the peak I observed was somewhere around 430MB). (after patch, in the kernel repository, master at 96b5a46) $ /usr/bin/time git-diff -M -l0 --name-status d19fbe8a7 master >/var/tmp/3 157.20user 1.03system 2:38.72elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (32major+237315minor)pagefaults 0swaps $ /usr/bin/time git-diff -M -l0 --name-status d19fbe8a7 master >/var/tmp/4 174.00user 2.73system 3:09.55elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (6106major+459314minor)pagefaults 0swaps So it is not that much of an improvement, but it seems to help somewhat. The first hunk is about shrinking the diff_score structure; before the patch, it was O(NxM) where N and M are number of rename source and destination candidates, but after the patch it is now O(M), so this shrinkage should not matter, but score is capped to MAX_SCORE (60000) and name_score is actually 0 or 1. We cannot make it 1-bit unsigned bitfield as there is a qsort comparison callback that does (b->name_score - a->name_score). We would need to see where the remaining 400MB is going and try to shrink it, but this would be an improvement so I'll soon be moving this to 'next'. --- diffcore-rename.c | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diffcore-rename.c b/diffcore-rename.c index 90d06f0..99953e7 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -112,8 +112,8 @@ static int basename_same(struct diff_filespec *src, struct diff_filespec *dst) struct diff_score { int src; /* index in rename_src */ int dst; /* index in rename_dst */ - int score; - int name_score; + unsigned short score; + short name_score; }; static int estimate_similarity(struct diff_filespec *src, @@ -393,7 +393,7 @@ static int find_exact_renames(void) return i; } -#define NUM_CANDIDATE_PER_DST 8 +#define NUM_CANDIDATE_PER_DST 4 static void record_if_better(struct diff_score m[], struct diff_score *o) { int i, worst; ^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-02-13 9:53 ` Junio C Hamano @ 2008-02-13 10:19 ` David Kastrup 2008-02-13 10:23 ` Junio C Hamano 2008-02-14 3:00 ` Junio C Hamano 1 sibling, 1 reply; 24+ messages in thread From: David Kastrup @ 2008-02-13 10:19 UTC (permalink / raw) To: git Junio C Hamano <gitster@pobox.com> writes: > The first hunk is about shrinking the diff_score structure; > before the patch, it was O(NxM) where N and M are number of > rename source and destination candidates, but after the patch it > is now O(M), so this shrinkage should not matter, but score is > capped to MAX_SCORE (60000) and name_score is actually 0 or 1. > We cannot make it 1-bit unsigned bitfield as there is a qsort > comparison callback that does (b->name_score - a->name_score). Hm? Can't that be changed into (b->name_score > a->name_score ? 1 : b->name_score < a->name_score ? -1 : 0) or something? Or perhaps just ((int)b->name_score - (int)a->name_score) or so? It sounds like a factor 2 is in question here, and that would seem like an easy fix? -- David Kastrup ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-02-13 10:19 ` David Kastrup @ 2008-02-13 10:23 ` Junio C Hamano 0 siblings, 0 replies; 24+ messages in thread From: Junio C Hamano @ 2008-02-13 10:23 UTC (permalink / raw) To: David Kastrup; +Cc: git David Kastrup <dak@gnu.org> writes: > Junio C Hamano <gitster@pobox.com> writes: > ... >> We cannot make it 1-bit unsigned bitfield as there is a qsort >> comparison callback that does (b->name_score - a->name_score). > > Hm? Can't that be changed into ... Sorry, my "we cannot" was misleading. We do not have anything else that we need to store that is 15-bit, so even if we can, it won't gain us anything. ^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH] Optimize rename detection for a huge diff 2008-02-13 9:53 ` Junio C Hamano 2008-02-13 10:19 ` David Kastrup @ 2008-02-14 3:00 ` Junio C Hamano 1 sibling, 0 replies; 24+ messages in thread From: Junio C Hamano @ 2008-02-14 3:00 UTC (permalink / raw) To: Linus Torvalds; +Cc: Jeff King, Adrian Bunk, git Junio C Hamano <gitster@pobox.com> writes: > With a bit of tweak, now I am getting these numbers to the > rename detection that used to spend 800MB (the peak I observed > was somewhere around 430MB). > > (after patch, in the kernel repository, master at 96b5a46) > $ /usr/bin/time git-diff -M -l0 --name-status d19fbe8a7 master >/var/tmp/3 > 157.20user 1.03system 2:38.72elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k > 0inputs+0outputs (32major+237315minor)pagefaults 0swaps > > (before patch, same diff) > $ /usr/bin/time git-diff -M -l0 --name-status d19fbe8a7 master >/var/tmp/4 > 174.00user 2.73system 3:09.55elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k > 0inputs+0outputs (6106major+459314minor)pagefaults 0swaps > > So it is not that much of an improvement, but it seems to help > somewhat. > ... > We would need to see where the remaining 400MB is going and try > to shrink it, but this would be an improvement so I'll soon be > moving this to 'next'. I noticed that massif reported diff_queue() very high in the list, so came up with this patch on top of the previous ones. 162.75user 1.92system 2:45.22elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+215490minor)pagefaults 0swaps Still not much of an improvement (10%, judging from the minor fault counts?), and it risks crashing if there is an unconvered free() that frees a diff_filepair that was obtained from the bulk allocator. --- alloc.c | 33 ++++++++++++++++++++ builtin-diff-tree.c | 2 +- builtin-rev-parse.c | 2 +- builtin-send-pack.c | 2 +- builtin-show-branch.c | 2 +- cache.h | 4 ++ commit.c | 14 ++++---- diff.c | 4 +- diffcore-break.c | 12 ++++--- diffcore-rename.c | 80 +++++++++++++++++++++++++++++++++++------------- diffcore.h | 4 ++ http-push.c | 2 +- revision.c | 6 ++-- upload-pack.c | 2 +- 14 files changed, 124 insertions(+), 45 deletions(-) diff --git a/alloc.c b/alloc.c index 216c23a..08c2591 100644 --- a/alloc.c +++ b/alloc.c @@ -15,6 +15,8 @@ #include "tree.h" #include "commit.h" #include "tag.h" +#include "diff.h" +#include "diffcore.h" #define BLOCKING 1024 @@ -51,6 +53,35 @@ DEFINE_ALLOCATOR(commit, struct commit) DEFINE_ALLOCATOR(tag, struct tag) DEFINE_ALLOCATOR(object, union any_object) +#define DEFINE_FREEABLE_ALLOCATOR(name, type) \ +DEFINE_ALLOCATOR(name, type) \ +union freeable_##name { \ + union freeable_##name *next; \ + type body; \ +}; \ +static union freeable_##name *name##_freepool; \ +type *alloc_freeable_##name##_node(void) \ +{ \ + if (name##_freepool) { \ + union freeable_##name *one = name##_freepool; \ + name##_freepool = one->next; \ + memset(one, 0, sizeof(*one)); \ + return &(one->body); \ + } \ + return alloc_##name##_node(); \ +} \ +void free_##name##_node(type *it_) \ +{ \ + union freeable_##name *it = (union freeable_##name *)it_; \ + if (it) { \ + it->next = name##_freepool; \ + name##_freepool = it; \ + } \ +} + +DEFINE_FREEABLE_ALLOCATOR(commit_list, struct commit_list) +DEFINE_FREEABLE_ALLOCATOR(diff_filepair, struct diff_filepair) + #ifdef NO_C99_FORMAT #define SZ_FMT "%u" #else @@ -73,4 +104,6 @@ void alloc_report(void) REPORT(tree); REPORT(commit); REPORT(tag); + REPORT(commit_list); } + diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index 832797f..04f93f5 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -36,7 +36,7 @@ static int diff_tree_stdin(char *line) /* Free the real parent list */ for (parents = commit->parents; parents; ) { struct commit_list *tmp = parents->next; - free(parents); + free_commit_list_node(parents); parents = tmp; } commit->parents = NULL; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index b9af1a5..580ec51 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -227,7 +227,7 @@ static int try_difference(const char *arg) struct commit_list *n = exclude->next; show_rev(REVERSED, exclude->item->object.sha1,NULL); - free(exclude); + free_commit_list_node(exclude); exclude = n; } } diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 8afb1d0..6a83258 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -82,7 +82,7 @@ static void unmark_and_free(struct commit_list *list, unsigned int mark) struct commit_list *temp = list; temp->item->object.flags &= ~mark; list = temp->next; - free(temp); + free_commit_list_node(temp); } } diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 019abd3..0d7cf89 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -38,7 +38,7 @@ static struct commit *pop_one_commit(struct commit_list **list_p) list = *list_p; commit = list->item; *list_p = list->next; - free(list); + free_commit_list_node(list); return commit; } diff --git a/cache.h b/cache.h index 3867ba7..196a0d7 100644 --- a/cache.h +++ b/cache.h @@ -667,6 +667,10 @@ extern void *alloc_tree_node(void); extern void *alloc_commit_node(void); extern void *alloc_tag_node(void); extern void *alloc_object_node(void); + +extern struct commit_list *alloc_freeable_commit_list_node(void); +extern void free_commit_list_node(struct commit_list *); + extern void alloc_report(void); /* trace.c */ diff --git a/commit.c b/commit.c index 8b8fb04..6696968 100644 --- a/commit.c +++ b/commit.c @@ -333,7 +333,7 @@ int parse_commit(struct commit *item) struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p) { - struct commit_list *new_list = xmalloc(sizeof(struct commit_list)); + struct commit_list *new_list = alloc_freeable_commit_list_node(); new_list->item = item; new_list->next = *list_p; *list_p = new_list; @@ -345,11 +345,11 @@ void free_commit_list(struct commit_list *list) while (list) { struct commit_list *temp = list; list = temp->next; - free(temp); + free_commit_list_node(temp); } } -struct commit_list * insert_by_date(struct commit *item, struct commit_list **list) +struct commit_list *insert_by_date(struct commit *item, struct commit_list **list) { struct commit_list **pp = list; struct commit_list *p; @@ -381,7 +381,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list, struct commit_list *old = *list; *list = (*list)->next; - free(old); + free_commit_list_node(old); while (parents) { struct commit *commit = parents->item; @@ -423,7 +423,7 @@ struct commit *pop_commit(struct commit_list **stack) if (top) { *stack = top->next; - free(top); + free_commit_list_node(top); } return item; } @@ -568,7 +568,7 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) commit = list->item; n = list->next; - free(list); + free_commit_list_node(list); list = n; flags = commit->object.flags & (PARENT1 | PARENT2 | STALE); @@ -599,7 +599,7 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two) struct commit_list *n = list->next; if (!(list->item->object.flags & STALE)) insert_by_date(list->item, &result); - free(list); + free_commit_list_node(list); list = n; } return result; diff --git a/diff.c b/diff.c index cd8bc4d..63ac8db 100644 --- a/diff.c +++ b/diff.c @@ -2433,7 +2433,7 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, struct diff_filespec *one, struct diff_filespec *two) { - struct diff_filepair *dp = xcalloc(1, sizeof(*dp)); + struct diff_filepair *dp = alloc_freeable_diff_filepair_node(); dp->one = one; dp->two = two; if (queue) @@ -2445,7 +2445,7 @@ void diff_free_filepair(struct diff_filepair *p) { free_filespec(p->one); free_filespec(p->two); - free(p); + free_diff_filepair_node(p); } /* This is different from find_unique_abbrev() in that diff --git a/diffcore-break.c b/diffcore-break.c index 31cdcfe..debd26d 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -205,9 +205,11 @@ void diffcore_break(int break_score) dp->score = score; dp->broken_pair = 1; - free(p); /* not diff_free_filepair(), we are - * reusing one and two here. - */ + /* + * not diff_free_filepair(), we are + * reusing one and two here. + */ + free_diff_filepair_node(p); continue; } } @@ -243,8 +245,8 @@ static void merge_broken(struct diff_filepair *p, dp->score = p->score; diff_free_filespec_data(d->two); diff_free_filespec_data(c->one); - free(d); - free(c); + free_diff_filepair_node(d); + free_diff_filepair_node(c); } void diffcore_merge_broken(void) diff --git a/diffcore-rename.c b/diffcore-rename.c index 3d37725..5974362 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -112,8 +112,8 @@ static int basename_same(struct diff_filespec *src, struct diff_filespec *dst) struct diff_score { int src; /* index in rename_src */ int dst; /* index in rename_dst */ - int score; - int name_score; + unsigned short score; + short name_score; }; static int estimate_similarity(struct diff_filespec *src, @@ -223,6 +223,12 @@ static int score_compare(const void *a_, const void *b_) { const struct diff_score *a = a_, *b = b_; + /* sink the unused ones to the bottom */ + if (a->dst < 0) + return (0 <= b->dst); + else if (b->dst < 0) + return -1; + if (a->score == b->score) return b->name_score - a->name_score; @@ -387,6 +393,22 @@ static int find_exact_renames(void) return i; } +#define NUM_CANDIDATE_PER_DST 4 +static void record_if_better(struct diff_score m[], struct diff_score *o) +{ + int i, worst; + + /* find the worst one */ + worst = 0; + for (i = 1; i < NUM_CANDIDATE_PER_DST; i++) + if (score_compare(&m[i], &m[worst]) > 0) + worst = i; + + /* is it better than the worst one? */ + if (score_compare(&m[worst], o) > 0) + m[worst] = *o; +} + void diffcore_rename(struct diff_options *options) { int detect_rename = options->detect_rename; @@ -473,47 +495,61 @@ void diffcore_rename(struct diff_options *options) if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - mx = xmalloc(sizeof(*mx) * num_create * num_src); + mx = xcalloc(num_create * NUM_CANDIDATE_PER_DST, sizeof(*mx)); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { - int base = dst_cnt * num_src; struct diff_filespec *two = rename_dst[i].two; + struct diff_score *m; + if (rename_dst[i].pair) continue; /* dealt with exact match already. */ + + m = &mx[dst_cnt * NUM_CANDIDATE_PER_DST]; + for (j = 0; j < NUM_CANDIDATE_PER_DST; j++) + m[j].dst = -1; + for (j = 0; j < rename_src_nr; j++) { struct diff_filespec *one = rename_src[j].one; - struct diff_score *m = &mx[base+j]; - m->src = j; - m->dst = i; - m->score = estimate_similarity(one, two, - minimum_score); - m->name_score = basename_same(one, two); + struct diff_score this_src; + this_src.score = estimate_similarity(one, two, + minimum_score); + this_src.name_score = basename_same(one, two); + this_src.dst = i; + this_src.src = j; + record_if_better(m, &this_src); diff_free_filespec_blob(one); } /* We do not need the text anymore */ diff_free_filespec_blob(two); dst_cnt++; } + /* cost matrix sorted by most to least similar pair */ - qsort(mx, num_create * num_src, sizeof(*mx), score_compare); - for (i = 0; i < num_create * num_src; i++) { - struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; - struct diff_filespec *src; + qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare); + + for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { + struct diff_rename_dst *dst; + + if ((mx[i].dst < 0) || + (mx[i].score < minimum_score)) + break; /* there is no more usable pair. */ + dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ - if (mx[i].score < minimum_score) - break; /* there is no more usable pair. */ - src = rename_src[mx[i].src].one; - if (src->rename_used) + if (rename_src[mx[i].src].one->rename_used) continue; record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); rename_count++; } - for (i = 0; i < num_create * num_src; i++) { - struct diff_rename_dst *dst = &rename_dst[mx[i].dst]; + + for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) { + struct diff_rename_dst *dst; + + if ((mx[i].dst < 0) || + (mx[i].score < minimum_score)) + break; /* there is no more usable pair. */ + dst = &rename_dst[mx[i].dst]; if (dst->pair) continue; /* already done, either exact or fuzzy. */ - if (mx[i].score < minimum_score) - break; /* there is no more usable pair. */ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); rename_count++; } diff --git a/diffcore.h b/diffcore.h index cc96c20..00aef4c 100644 --- a/diffcore.h +++ b/diffcore.h @@ -118,4 +118,8 @@ extern int diffcore_count_changes(struct diff_filespec *src, unsigned long *src_copied, unsigned long *literal_added); +/* alloc.c */ +extern struct diff_filepair *alloc_freeable_diff_filepair_node(void); +extern void free_diff_filepair_node(struct diff_filepair *); + #endif diff --git a/http-push.c b/http-push.c index b2b410d..314141f 100644 --- a/http-push.c +++ b/http-push.c @@ -1817,7 +1817,7 @@ static void unmark_and_free(struct commit_list *list, unsigned int mark) struct commit_list *temp = list; temp->item->object.flags &= ~mark; list = temp->next; - free(temp); + free_commit_list_node(temp); } } diff --git a/revision.c b/revision.c index 6e85aaa..df9b062 100644 --- a/revision.c +++ b/revision.c @@ -571,7 +571,7 @@ static int limit_list(struct rev_info *revs) show_early_output_fn_t show; list = list->next; - free(entry); + free_commit_list_node(entry); if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; @@ -752,7 +752,7 @@ static void prepare_show_merge(struct rev_info *revs) while (bases) { struct commit *it = bases->item; struct commit_list *n = bases->next; - free(bases); + free_commit_list_node(bases); bases = n; it->object.flags |= UNINTERESTING; add_pending_object(revs, &it->object, "(merge-base)"); @@ -1472,7 +1472,7 @@ static struct commit *get_revision_1(struct rev_info *revs) struct commit *commit = entry->item; revs->commits = entry->next; - free(entry); + free_commit_list_node(entry); if (revs->reflog_info) fake_reflog_parent(revs->reflog_info, commit); diff --git a/upload-pack.c b/upload-pack.c index 51e3ec4..86a4e7e 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -331,7 +331,7 @@ static int reachable(struct commit *want) while (work) { struct commit_list *list = work->next; struct commit *commit = work->item; - free(work); + free_commit_list_node(work); work = list; if (commit->object.flags & THEY_HAVE) { ^ permalink raw reply related [flat|nested] 24+ messages in thread
end of thread, other threads:[~2008-02-14 3:00 UTC | newest] Thread overview: 24+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2008-01-27 17:27 git-revert is a memory hog Adrian Bunk 2008-01-27 17:38 ` Shawn O. Pearce 2008-01-28 6:01 ` Jeff King 2008-01-28 5:59 ` Jeff King 2008-01-29 21:51 ` Linus Torvalds 2008-01-29 22:15 ` Junio C Hamano 2008-01-29 22:20 ` Jeff King 2008-01-29 22:30 ` Jeff King 2008-01-29 22:36 ` Junio C Hamano 2008-01-29 22:45 ` Jeff King 2008-01-29 22:51 ` Jeff King 2008-01-29 22:49 ` Linus Torvalds 2008-01-29 22:54 ` Jeff King 2008-01-29 22:53 ` Junio C Hamano 2008-01-29 22:57 ` Jeff King 2008-01-29 23:19 ` Junio C Hamano 2008-01-29 23:50 ` Junio C Hamano 2008-01-30 4:40 ` [PATCH] Optimize rename detection for a huge diff Junio C Hamano 2008-01-30 6:57 ` Luke Lu 2008-01-30 7:24 ` Luke Lu 2008-02-13 9:53 ` Junio C Hamano 2008-02-13 10:19 ` David Kastrup 2008-02-13 10:23 ` Junio C Hamano 2008-02-14 3:00 ` Junio C Hamano
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).