commit: allow associating auxiliary info on-demand

The "indegree" field in the commit object is only used while sorting a list of commits in topological order, and wasting memory otherwise. We would prefer to shrink the size of individual commit objects, which we may have to hold thousands of in-core. We could eject "indegree" field out from the commit object and represent it as a dynamic table based on the decoration infrastructure, but the decoration is meant for sparse annotation and is not a good match. Instead, let's try a different approach. - Assign an integer (commit->index) to each commit we keep in-core (reuse the space of "indegree" field for it); - When running the topological sort, allocate an array of integers in bulk (called "slab"), use the commit->index as an index into this array, and store the "indegree" information there. This does _not_ reduce the memory footprint of a commit object, but the commit->index can be used as the index to dynamically associate commits with other kinds of information as needed. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Apr 9, 2013 at 02:52 UTC 96c4f4a370591b4796628abe18f27f0133b21954
2 files changed +51 -10
commit.c
+50 -9
@@ -14,6 +14,7 @@ static struct commit_extra_header *read_commit_extra_header_lines(const char *bu
14 int save_commit_buffer = 1;
15
16 const char *commit_type = "commit";
17 +static int commit_count;
18
19 static struct commit *check_commit(struct object *obj,
20 const unsigned char *sha1,
@@ -58,8 +59,11 @@ struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_n
59 struct commit *lookup_commit(const unsigned char *sha1)
60 {
61 struct object *obj = lookup_object(sha1);
61 - if (!obj)
62 - return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
62 + if (!obj) {
63 + struct commit *c = alloc_commit_node();
64 + c->index = commit_count++;
65 + return create_object(sha1, OBJ_COMMIT, c);
66 + }
67 if (!obj->type)
68 obj->type = OBJ_COMMIT;
69 return check_commit(obj, sha1, 0);
@@ -497,6 +501,36 @@ struct commit *pop_commit(struct commit_list **stack)
501 return item;
502 }
503
504 +struct commit_slab {
505 + int *buf;
506 + int alloc;
507 +};
508 +
509 +static void slab_init(struct commit_slab *s)
510 +{
511 + memset(s, 0, sizeof(*s));
512 +}
513 +
514 +static void slab_clear(struct commit_slab *s)
515 +{
516 + free(s->buf);
517 + slab_init(s);
518 +}
519 +
520 +static inline int *slab_at(struct commit_slab *s, const struct commit *c)
521 +{
522 + if (s->alloc <= c->index) {
523 + int new_alloc = alloc_nr(s->alloc);
524 + if (new_alloc <= c->index)
525 + new_alloc = c->index + 1;
526 +
527 + s->buf = xrealloc(s->buf, new_alloc * sizeof(*s->buf));
528 + memset(s->buf + s->alloc, 0, new_alloc - s->alloc);
529 + s->alloc = new_alloc;
530 + }
531 + return s->buf + c->index;
532 +}
533 +
534 /*
535 * Performs an in-place topological sort on the list supplied.
536 */
@@ -505,15 +539,18 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
539 struct commit_list *next, *orig = *list;
540 struct commit_list *work, **insert;
541 struct commit_list **pptr;
542 + struct commit_slab indegree;
543
544 if (!orig)
545 return;
546 *list = NULL;
547
548 + slab_init(&indegree);
549 +
550 /* Mark them and clear the indegree */
551 for (next = orig; next; next = next->next) {
552 struct commit *commit = next->item;
516 - commit->indegree = 1;
553 + *slab_at(&indegree, commit) = 1;
554 }
555
556 /* update the indegree */
@@ -521,9 +558,10 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
558 struct commit_list * parents = next->item->parents;
559 while (parents) {
560 struct commit *parent = parents->item;
561 + int *pi = slab_at(&indegree, parent);
562
525 - if (parent->indegree)
526 - parent->indegree++;
563 + if (*pi)
564 + (*pi)++;
565 parents = parents->next;
566 }
567 }
@@ -540,7 +578,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
578 for (next = orig; next; next = next->next) {
579 struct commit *commit = next->item;
580
543 - if (commit->indegree == 1)
581 + if (*slab_at(&indegree, commit) == 1)
582 insert = &commit_list_insert(commit, insert)->next;
583 }
584
@@ -561,8 +599,9 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
599 commit = work_item->item;
600 for (parents = commit->parents; parents ; parents = parents->next) {
601 struct commit *parent = parents->item;
602 + int *pi = slab_at(&indegree, parent);
603
565 - if (!parent->indegree)
604 + if (!*pi)
605 continue;
606
607 /*
@@ -570,7 +609,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
609 * when all their children have been emitted thereby
610 * guaranteeing topological order.
611 */
573 - if (--parent->indegree == 1) {
612 + if (--(*pi) == 1) {
613 if (!lifo)
614 commit_list_insert_by_date(parent, &work);
615 else
@@ -581,10 +620,12 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
620 * work_item is a commit all of whose children
621 * have already been emitted. we can emit it now.
622 */
584 - commit->indegree = 0;
623 + *slab_at(&indegree, commit) = 0;
624 *pptr = work_item;
625 pptr = &work_item->next;
626 }
627 +
628 + slab_clear(&indegree);
629 }
630
631 /* merge-base stuff */
commit.h
+1 -1
@@ -14,7 +14,7 @@ struct commit_list {
14 struct commit {
15 struct object object;
16 void *util;
17 - unsigned int indegree;
17 + unsigned int index;
18 unsigned long date;
19 struct commit_list *parents;
20 struct tree *tree;