index-pack: kill union delta_base to save memory

Once we know the number of objects in the input pack, we allocate an array of nr_objects of struct delta_entry. On x86-64, this struct is 32 bytes long. The union delta_base, which is part of struct delta_entry, provides enough space to store either ofs-delta (8 bytes) or ref-delta (20 bytes). Because ofs-delta encoding is more efficient space-wise and more performant at runtime than ref-delta encoding, Git packers try to use ofs-delta whenever possible, and it is expected that objects encoded as ref-delta are minority. In the best clone case where no ref-delta object is present, we waste (20-8) * nr_objects bytes because of this union. That's about 38MB out of 100MB for deltas[] with 3.4M objects, or 38%. deltas[] would be around 62MB without the waste. This patch attempts to eliminate that. deltas[] array is split into two: one for ofs-delta and one for ref-delta. Many functions are also duplicated because of this split. With this patch, ofs_deltas[] array takes 51MB. ref_deltas[] should remain unallocated in clone case (0 bytes). This array grows as we see ref-delta. We save about half in this case, or 25% of total bookkeeping. The saving is more than the calculation above because some padding in the old delta_entry struct is removed. ofs_delta_entry is 16 bytes, including the 4 bytes padding. That's 13MB for padding, but packing the struct could break platforms that do not support unaligned access. If someone on 32-bit is really low on memory and only deals with packs smaller than 2G, using 32-bit off_t would eliminate the padding and save 27MB on top. A note about ofs_deltas allocation. We could use ref_deltas memory allocation strategy for ofs_deltas. But that probably just adds more overhead on top. ofs-deltas are generally the majority (1/2 to 2/3) in any pack. Incremental realloc may lead to too many memcpy. And if we preallocate, say 1/2 or 2/3 of nr_objects initially, the growth rate of ALLOC_GROW() could make this array larger than nr_objects, wasting more memory. Brought-up-by: Matthew Sporleder <msporleder@gmail.com> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Apr 18, 2015 at 17:47 UTC c6458e60ed0f3e26a1df88bf5a3da8b091b0ce15
1 file changed +160 -100
builtin/index-pack.c
+160 -100
@@ -28,11 +28,6 @@ struct object_stat {
28 int base_object_no;
29 };
30
31 -union delta_base {
32 - unsigned char sha1[20];
33 - off_t offset;
34 -};
35 -
31 struct base_data {
32 struct base_data *base;
33 struct base_data *child;
@@ -52,26 +47,28 @@ struct thread_local {
47 int pack_fd;
48 };
49
55 -/*
56 - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
57 - * to memcmp() only the first 20 bytes.
58 - */
59 -#define UNION_BASE_SZ 20
60 -
50 #define FLAG_LINK (1u<<20)
51 #define FLAG_CHECKED (1u<<21)
52
64 -struct delta_entry {
65 - union delta_base base;
53 +struct ofs_delta_entry {
54 + off_t offset;
55 + int obj_no;
56 +};
57 +
58 +struct ref_delta_entry {
59 + unsigned char sha1[20];
60 int obj_no;
61 };
62
63 static struct object_entry *objects;
64 static struct object_stat *obj_stat;
71 -static struct delta_entry *deltas;
65 +static struct ofs_delta_entry *ofs_deltas;
66 +static struct ref_delta_entry *ref_deltas;
67 static struct thread_local nothread_data;
68 static int nr_objects;
74 -static int nr_deltas;
69 +static int nr_ofs_deltas;
70 +static int nr_ref_deltas;
71 +static int ref_deltas_alloc;
72 static int nr_resolved_deltas;
73 static int nr_threads;
74
@@ -480,7 +477,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
477 }
478
479 static void *unpack_raw_entry(struct object_entry *obj,
483 - union delta_base *delta_base,
480 + off_t *ofs_offset,
481 + unsigned char *ref_sha1,
482 unsigned char *sha1)
483 {
484 unsigned char *p;
@@ -509,11 +507,10 @@ static void *unpack_raw_entry(struct object_entry *obj,
507
508 switch (obj->type) {
509 case OBJ_REF_DELTA:
512 - hashcpy(delta_base->sha1, fill(20));
510 + hashcpy(ref_sha1, fill(20));
511 use(20);
512 break;
513 case OBJ_OFS_DELTA:
516 - memset(delta_base, 0, sizeof(*delta_base));
514 p = fill(1);
515 c = *p;
516 use(1);
@@ -527,8 +524,8 @@ static void *unpack_raw_entry(struct object_entry *obj,
524 use(1);
525 base_offset = (base_offset << 7) + (c & 127);
526 }
530 - delta_base->offset = obj->idx.offset - base_offset;
531 - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
527 + *ofs_offset = obj->idx.offset - base_offset;
528 + if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset)
529 bad_object(obj->idx.offset, _("delta base offset is out of bound"));
530 break;
531 case OBJ_COMMIT:
@@ -612,55 +609,108 @@ static void *get_data_from_pack(struct object_entry *obj)
609 return unpack_data(obj, NULL, NULL);
610 }
611
615 -static int compare_delta_bases(const union delta_base *base1,
616 - const union delta_base *base2,
617 - enum object_type type1,
618 - enum object_type type2)
612 +static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
613 + enum object_type type1,
614 + enum object_type type2)
615 {
616 int cmp = type1 - type2;
617 if (cmp)
618 return cmp;
623 - return memcmp(base1, base2, UNION_BASE_SZ);
619 + return offset1 - offset2;
620 }
621
626 -static int find_delta(const union delta_base *base, enum object_type type)
622 +static int find_ofs_delta(const off_t offset, enum object_type type)
623 {
628 - int first = 0, last = nr_deltas;
629 -
630 - while (first < last) {
631 - int next = (first + last) / 2;
632 - struct delta_entry *delta = &deltas[next];
633 - int cmp;
634 -
635 - cmp = compare_delta_bases(base, &delta->base,
636 - type, objects[delta->obj_no].type);
637 - if (!cmp)
638 - return next;
639 - if (cmp < 0) {
640 - last = next;
641 - continue;
642 - }
643 - first = next+1;
644 - }
645 - return -first-1;
624 + int first = 0, last = nr_ofs_deltas;
625 +
626 + while (first < last) {
627 + int next = (first + last) / 2;
628 + struct ofs_delta_entry *delta = &ofs_deltas[next];
629 + int cmp;
630 +
631 + cmp = compare_ofs_delta_bases(offset, delta->offset,
632 + type, objects[delta->obj_no].type);
633 + if (!cmp)
634 + return next;
635 + if (cmp < 0) {
636 + last = next;
637 + continue;
638 + }
639 + first = next+1;
640 + }
641 + return -first-1;
642 }
643
648 -static void find_delta_children(const union delta_base *base,
649 - int *first_index, int *last_index,
650 - enum object_type type)
644 +static void find_ofs_delta_children(off_t offset,
645 + int *first_index, int *last_index,
646 + enum object_type type)
647 {
652 - int first = find_delta(base, type);
648 + int first = find_ofs_delta(offset, type);
649 int last = first;
654 - int end = nr_deltas - 1;
650 + int end = nr_ofs_deltas - 1;
651
652 if (first < 0) {
653 *first_index = 0;
654 *last_index = -1;
655 return;
656 }
661 - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
657 + while (first > 0 && ofs_deltas[first - 1].offset == offset)
658 --first;
663 - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
659 + while (last < end && ofs_deltas[last + 1].offset == offset)
660 + ++last;
661 + *first_index = first;
662 + *last_index = last;
663 +}
664 +
665 +static int compare_ref_delta_bases(const unsigned char *sha1,
666 + const unsigned char *sha2,
667 + enum object_type type1,
668 + enum object_type type2)
669 +{
670 + int cmp = type1 - type2;
671 + if (cmp)
672 + return cmp;
673 + return hashcmp(sha1, sha2);
674 +}
675 +
676 +static int find_ref_delta(const unsigned char *sha1, enum object_type type)
677 +{
678 + int first = 0, last = nr_ref_deltas;
679 +
680 + while (first < last) {
681 + int next = (first + last) / 2;
682 + struct ref_delta_entry *delta = &ref_deltas[next];
683 + int cmp;
684 +
685 + cmp = compare_ref_delta_bases(sha1, delta->sha1,
686 + type, objects[delta->obj_no].type);
687 + if (!cmp)
688 + return next;
689 + if (cmp < 0) {
690 + last = next;
691 + continue;
692 + }
693 + first = next+1;
694 + }
695 + return -first-1;
696 +}
697 +
698 +static void find_ref_delta_children(const unsigned char *sha1,
699 + int *first_index, int *last_index,
700 + enum object_type type)
701 +{
702 + int first = find_ref_delta(sha1, type);
703 + int last = first;
704 + int end = nr_ref_deltas - 1;
705 +
706 + if (first < 0) {
707 + *first_index = 0;
708 + *last_index = -1;
709 + return;
710 + }
711 + while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1))
712 + --first;
713 + while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1))
714 ++last;
715 *first_index = first;
716 *last_index = last;
@@ -927,16 +977,13 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
977 struct base_data *prev_base)
978 {
979 if (base->ref_last == -1 && base->ofs_last == -1) {
930 - union delta_base base_spec;
931 -
932 - hashcpy(base_spec.sha1, base->obj->idx.sha1);
933 - find_delta_children(&base_spec,
934 - &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
980 + find_ref_delta_children(base->obj->idx.sha1,
981 + &base->ref_first, &base->ref_last,
982 + OBJ_REF_DELTA);
983
936 - memset(&base_spec, 0, sizeof(base_spec));
937 - base_spec.offset = base->obj->idx.offset;
938 - find_delta_children(&base_spec,
939 - &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
984 + find_ofs_delta_children(base->obj->idx.offset,
985 + &base->ofs_first, &base->ofs_last,
986 + OBJ_OFS_DELTA);
987
988 if (base->ref_last == -1 && base->ofs_last == -1) {
989 free(base->data);
@@ -947,7 +994,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
994 }
995
996 if (base->ref_first <= base->ref_last) {
950 - struct object_entry *child = objects + deltas[base->ref_first].obj_no;
997 + struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
998 struct base_data *result = alloc_base_data();
999
1000 if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
@@ -963,7 +1010,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
1010 }
1011
1012 if (base->ofs_first <= base->ofs_last) {
966 - struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
1013 + struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
1014 struct base_data *result = alloc_base_data();
1015
1016 assert(child->real_type == OBJ_OFS_DELTA);
@@ -999,15 +1046,20 @@ static void find_unresolved_deltas(struct base_data *base)
1046 }
1047 }
1048
1002 -static int compare_delta_entry(const void *a, const void *b)
1049 +static int compare_ofs_delta_entry(const void *a, const void *b)
1050 +{
1051 + const struct ofs_delta_entry *delta_a = a;
1052 + const struct ofs_delta_entry *delta_b = b;
1053 +
1054 + return delta_a->offset - delta_b->offset;
1055 +}
1056 +
1057 +static int compare_ref_delta_entry(const void *a, const void *b)
1058 {
1004 - const struct delta_entry *delta_a = a;
1005 - const struct delta_entry *delta_b = b;
1059 + const struct ref_delta_entry *delta_a = a;
1060 + const struct ref_delta_entry *delta_b = b;
1061
1007 - /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
1008 - return compare_delta_bases(&delta_a->base, &delta_b->base,
1009 - objects[delta_a->obj_no].type,
1010 - objects[delta_b->obj_no].type);
1062 + return hashcmp(delta_a->sha1, delta_b->sha1);
1063 }
1064
1065 static void resolve_base(struct object_entry *obj)
@@ -1053,7 +1105,8 @@ static void *threaded_second_pass(void *data)
1105 static void parse_pack_objects(unsigned char *sha1)
1106 {
1107 int i, nr_delays = 0;
1056 - struct delta_entry *delta = deltas;
1108 + struct ofs_delta_entry *ofs_delta = ofs_deltas;
1109 + unsigned char ref_delta_sha1[20];
1110 struct stat st;
1111
1112 if (verbose)
@@ -1062,12 +1115,18 @@ static void parse_pack_objects(unsigned char *sha1)
1115 nr_objects);
1116 for (i = 0; i < nr_objects; i++) {
1117 struct object_entry *obj = &objects[i];
1065 - void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
1118 + void *data = unpack_raw_entry(obj, &ofs_delta->offset,
1119 + ref_delta_sha1, obj->idx.sha1);
1120 obj->real_type = obj->type;
1067 - if (is_delta_type(obj->type)) {
1068 - nr_deltas++;
1069 - delta->obj_no = i;
1070 - delta++;
1121 + if (obj->type == OBJ_OFS_DELTA) {
1122 + nr_ofs_deltas++;
1123 + ofs_delta->obj_no = i;
1124 + ofs_delta++;
1125 + } else if (obj->type == OBJ_REF_DELTA) {
1126 + ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc);
1127 + hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1);
1128 + ref_deltas[nr_ref_deltas].obj_no = i;
1129 + nr_ref_deltas++;
1130 } else if (!data) {
1131 /* large blobs, check later */
1132 obj->real_type = OBJ_BAD;
@@ -1118,15 +1177,18 @@ static void resolve_deltas(void)
1177 {
1178 int i;
1179
1121 - if (!nr_deltas)
1180 + if (!nr_ofs_deltas && !nr_ref_deltas)
1181 return;
1182
1183 /* Sort deltas by base SHA1/offset for fast searching */
1125 - qsort(deltas, nr_deltas, sizeof(struct delta_entry),
1126 - compare_delta_entry);
1184 + qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry),
1185 + compare_ofs_delta_entry);
1186 + qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry),
1187 + compare_ref_delta_entry);
1188
1189 if (verbose)
1129 - progress = start_progress(_("Resolving deltas"), nr_deltas);
1190 + progress = start_progress(_("Resolving deltas"),
1191 + nr_ref_deltas + nr_ofs_deltas);
1192
1193 #ifndef NO_PTHREADS
1194 nr_dispatched = 0;
@@ -1164,7 +1226,7 @@ static void resolve_deltas(void)
1226 static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
1227 static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
1228 {
1167 - if (nr_deltas == nr_resolved_deltas) {
1229 + if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) {
1230 stop_progress(&progress);
1231 /* Flush remaining pack final 20-byte SHA1. */
1232 flush();
@@ -1175,7 +1237,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
1237 struct sha1file *f;
1238 unsigned char read_sha1[20], tail_sha1[20];
1239 struct strbuf msg = STRBUF_INIT;
1178 - int nr_unresolved = nr_deltas - nr_resolved_deltas;
1240 + int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas;
1241 int nr_objects_initial = nr_objects;
1242 if (nr_unresolved <= 0)
1243 die(_("confusion beyond insanity"));
@@ -1197,11 +1259,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
1259 die(_("Unexpected tail checksum for %s "
1260 "(disk corruption?)"), curr_pack);
1261 }
1200 - if (nr_deltas != nr_resolved_deltas)
1262 + if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas)
1263 die(Q_("pack has %d unresolved delta",
1264 "pack has %d unresolved deltas",
1203 - nr_deltas - nr_resolved_deltas),
1204 - nr_deltas - nr_resolved_deltas);
1265 + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas),
1266 + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas);
1267 }
1268
1269 static int write_compressed(struct sha1file *f, void *in, unsigned int size)
@@ -1261,14 +1323,14 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f,
1323
1324 static int delta_pos_compare(const void *_a, const void *_b)
1325 {
1264 - struct delta_entry *a = *(struct delta_entry **)_a;
1265 - struct delta_entry *b = *(struct delta_entry **)_b;
1326 + struct ref_delta_entry *a = *(struct ref_delta_entry **)_a;
1327 + struct ref_delta_entry *b = *(struct ref_delta_entry **)_b;
1328 return a->obj_no - b->obj_no;
1329 }
1330
1331 static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
1332 {
1271 - struct delta_entry **sorted_by_pos;
1333 + struct ref_delta_entry **sorted_by_pos;
1334 int i, n = 0;
1335
1336 /*
@@ -1282,28 +1344,25 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
1344 * resolving deltas in the same order as their position in the pack.
1345 */
1346 sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
1285 - for (i = 0; i < nr_deltas; i++) {
1286 - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
1287 - continue;
1288 - sorted_by_pos[n++] = &deltas[i];
1289 - }
1347 + for (i = 0; i < nr_ref_deltas; i++)
1348 + sorted_by_pos[n++] = &ref_deltas[i];
1349 qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
1350
1351 for (i = 0; i < n; i++) {
1293 - struct delta_entry *d = sorted_by_pos[i];
1352 + struct ref_delta_entry *d = sorted_by_pos[i];
1353 enum object_type type;
1354 struct base_data *base_obj = alloc_base_data();
1355
1356 if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
1357 continue;
1299 - base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
1358 + base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size);
1359 if (!base_obj->data)
1360 continue;
1361
1303 - if (check_sha1_signature(d->base.sha1, base_obj->data,
1362 + if (check_sha1_signature(d->sha1, base_obj->data,
1363 base_obj->size, typename(type)))
1305 - die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
1306 - base_obj->obj = append_obj_to_pack(f, d->base.sha1,
1364 + die(_("local object %s is corrupt"), sha1_to_hex(d->sha1));
1365 + base_obj->obj = append_obj_to_pack(f, d->sha1,
1366 base_obj->data, base_obj->size, type);
1367 find_unresolved_deltas(base_obj);
1368 display_progress(progress, nr_resolved_deltas);
@@ -1495,7 +1554,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
1554
1555 static void show_pack_info(int stat_only)
1556 {
1498 - int i, baseobjects = nr_objects - nr_deltas;
1557 + int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas;
1558 unsigned long *chain_histogram = NULL;
1559
1560 if (deepest_delta)
@@ -1680,11 +1739,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
1739 objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
1740 if (show_stat)
1741 obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat));
1683 - deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
1742 + ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
1743 parse_pack_objects(pack_sha1);
1744 resolve_deltas();
1745 conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
1687 - free(deltas);
1746 + free(ofs_deltas);
1747 + free(ref_deltas);
1748 if (strict)
1749 foreign_nr = check_objects();
1750