@matheusbsbs / matheus-bagliano / commits / bb514de356
Fork 0

pack-objects: improve partial packfile reuse

The old code to reuse deltas from an existing packfile just tried to dump a whole segment of the pack verbatim. That's faster than the traditional way of actually adding objects to the packing list, but it didn't kick in very often. This new code is really going for a middle ground: do _some_ per-object work, but way less than we'd traditionally do. The general strategy of the new code is to make a bitmap of objects from the packfile we'll include, and then iterate over it, writing out each object exactly as it is in our on-disk pack, but _not_ adding it to our packlist (which costs memory, and increases the search space for deltas). One complication is that if we're omitting some objects, we can't set a delta against a base that we're not sending. So we have to check each object in try_partial_reuse() to make sure we have its delta. About performance, in the worst case we might have interleaved objects that we are sending or not sending, and we'd have as many chunks as objects. But in practice we send big chunks. For instance, packing torvalds/linux on GitHub servers now reused 6.5M objects, but only needed ~50k chunks. Helped-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Dec 18, 2019 at 12:25 UTC bb514de356cfcfd314f7ac7ae1acfeede3fa4b1f
3 files changed +281 -86
builtin/pack-objects.c
+170 -44
@@ -92,7 +92,7 @@ static struct progress *progress_state;
92
93 static struct packed_git *reuse_packfile;
94 static uint32_t reuse_packfile_objects;
95 -static off_t reuse_packfile_offset;
95 +static struct bitmap *reuse_packfile_bitmap;
96
97 static int use_bitmap_index_default = 1;
98 static int use_bitmap_index = -1;
@@ -785,57 +785,185 @@ static struct object_entry **compute_write_order(void)
785 return wo;
786 }
787
788 -static off_t write_reused_pack(struct hashfile *f)
788 +
789 +/*
790 + * A reused set of objects. All objects in a chunk have the same
791 + * relative position in the original packfile and the generated
792 + * packfile.
793 + */
794 +
795 +static struct reused_chunk {
796 + /* The offset of the first object of this chunk in the original
797 + * packfile. */
798 + off_t original;
799 + /* The offset of the first object of this chunk in the generated
800 + * packfile minus "original". */
801 + off_t difference;
802 +} *reused_chunks;
803 +static int reused_chunks_nr;
804 +static int reused_chunks_alloc;
805 +
806 +static void record_reused_object(off_t where, off_t offset)
807 +{
808 + if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset)
809 + return;
810 +
811 + ALLOC_GROW(reused_chunks, reused_chunks_nr + 1,
812 + reused_chunks_alloc);
813 + reused_chunks[reused_chunks_nr].original = where;
814 + reused_chunks[reused_chunks_nr].difference = offset;
815 + reused_chunks_nr++;
816 +}
817 +
818 +/*
819 + * Binary search to find the chunk that "where" is in. Note
820 + * that we're not looking for an exact match, just the first
821 + * chunk that contains it (which implicitly ends at the start
822 + * of the next chunk.
823 + */
824 +static off_t find_reused_offset(off_t where)
825 {
790 - unsigned char buffer[8192];
791 - off_t to_write, total;
792 - int fd;
826 + int lo = 0, hi = reused_chunks_nr;
827 + while (lo < hi) {
828 + int mi = lo + ((hi - lo) / 2);
829 + if (where == reused_chunks[mi].original)
830 + return reused_chunks[mi].difference;
831 + if (where < reused_chunks[mi].original)
832 + hi = mi;
833 + else
834 + lo = mi + 1;
835 + }
836
794 - if (!is_pack_valid(reuse_packfile))
795 - die(_("packfile is invalid: %s"), reuse_packfile->pack_name);
837 + /*
838 + * The first chunk starts at zero, so we can't have gone below
839 + * there.
840 + */
841 + assert(lo);
842 + return reused_chunks[lo-1].difference;
843 +}
844
797 - fd = git_open(reuse_packfile->pack_name);
798 - if (fd < 0)
799 - die_errno(_("unable to open packfile for reuse: %s"),
800 - reuse_packfile->pack_name);
845 +static void write_reused_pack_one(size_t pos, struct hashfile *out,
846 + struct pack_window **w_curs)
847 +{
848 + off_t offset, next, cur;
849 + enum object_type type;
850 + unsigned long size;
851
802 - if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
803 - die_errno(_("unable to seek in reused packfile"));
852 + offset = reuse_packfile->revindex[pos].offset;
853 + next = reuse_packfile->revindex[pos + 1].offset;
854
805 - if (reuse_packfile_offset < 0)
806 - reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
855 + record_reused_object(offset, offset - hashfile_total(out));
856
808 - total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
857 + cur = offset;
858 + type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
859 + assert(type >= 0);
860
810 - while (to_write) {
811 - int read_pack = xread(fd, buffer, sizeof(buffer));
861 + if (type == OBJ_OFS_DELTA) {
862 + off_t base_offset;
863 + off_t fixup;
864 +
865 + unsigned char header[MAX_PACK_OBJECT_HEADER];
866 + unsigned len;
867 +
868 + base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset);
869 + assert(base_offset != 0);
870 +
871 + /* Convert to REF_DELTA if we must... */
872 + if (!allow_ofs_delta) {
873 + int base_pos = find_revindex_position(reuse_packfile, base_offset);
874 + const unsigned char *base_sha1 =
875 + nth_packed_object_sha1(reuse_packfile,
876 + reuse_packfile->revindex[base_pos].nr);
877 +
878 + len = encode_in_pack_object_header(header, sizeof(header),
879 + OBJ_REF_DELTA, size);
880 + hashwrite(out, header, len);
881 + hashwrite(out, base_sha1, 20);
882 + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
883 + return;
884 + }
885
813 - if (read_pack <= 0)
814 - die_errno(_("unable to read from reused packfile"));
886 + /* Otherwise see if we need to rewrite the offset... */
887 + fixup = find_reused_offset(offset) -
888 + find_reused_offset(base_offset);
889 + if (fixup) {
890 + unsigned char ofs_header[10];
891 + unsigned i, ofs_len;
892 + off_t ofs = offset - base_offset - fixup;
893
816 - if (read_pack > to_write)
817 - read_pack = to_write;
894 + len = encode_in_pack_object_header(header, sizeof(header),
895 + OBJ_OFS_DELTA, size);
896
819 - hashwrite(f, buffer, read_pack);
820 - to_write -= read_pack;
897 + i = sizeof(ofs_header) - 1;
898 + ofs_header[i] = ofs & 127;
899 + while (ofs >>= 7)
900 + ofs_header[--i] = 128 | (--ofs & 127);
901 +
902 + ofs_len = sizeof(ofs_header) - i;
903 +
904 + hashwrite(out, header, len);
905 + hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len);
906 + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
907 + return;
908 + }
909 +
910 + /* ...otherwise we have no fixup, and can write it verbatim */
911 + }
912 +
913 + copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset);
914 +}
915 +
916 +static size_t write_reused_pack_verbatim(struct hashfile *out,
917 + struct pack_window **w_curs)
918 +{
919 + size_t pos = 0;
920 +
921 + while (pos < reuse_packfile_bitmap->word_alloc &&
922 + reuse_packfile_bitmap->words[pos] == (eword_t)~0)
923 + pos++;
924 +
925 + if (pos) {
926 + off_t to_write;
927 +
928 + written = (pos * BITS_IN_EWORD);
929 + to_write = reuse_packfile->revindex[written].offset
930 + - sizeof(struct pack_header);
931 +
932 + /* We're recording one chunk, not one object. */
933 + record_reused_object(sizeof(struct pack_header), 0);
934 + hashflush(out);
935 + copy_pack_data(out, reuse_packfile, w_curs,
936 + sizeof(struct pack_header), to_write);
937
822 - /*
823 - * We don't know the actual number of objects written,
824 - * only how many bytes written, how many bytes total, and
825 - * how many objects total. So we can fake it by pretending all
826 - * objects we are writing are the same size. This gives us a
827 - * smooth progress meter, and at the end it matches the true
828 - * answer.
829 - */
830 - written = reuse_packfile_objects *
831 - (((double)(total - to_write)) / total);
938 display_progress(progress_state, written);
939 }
940 + return pos;
941 +}
942 +
943 +static void write_reused_pack(struct hashfile *f)
944 +{
945 + size_t i = 0;
946 + uint32_t offset;
947 + struct pack_window *w_curs = NULL;
948 +
949 + if (allow_ofs_delta)
950 + i = write_reused_pack_verbatim(f, &w_curs);
951 +
952 + for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
953 + eword_t word = reuse_packfile_bitmap->words[i];
954 + size_t pos = (i * BITS_IN_EWORD);
955 +
956 + for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
957 + if ((word >> offset) == 0)
958 + break;
959 +
960 + offset += ewah_bit_ctz64(word >> offset);
961 + write_reused_pack_one(pos + offset, f, &w_curs);
962 + display_progress(progress_state, ++written);
963 + }
964 + }
965
835 - close(fd);
836 - written = reuse_packfile_objects;
837 - display_progress(progress_state, written);
838 - return reuse_packfile_offset - sizeof(struct pack_header);
966 + unuse_pack(&w_curs);
967 }
968
969 static const char no_split_warning[] = N_(
@@ -868,11 +996,9 @@ static void write_pack_file(void)
996 offset = write_pack_header(f, nr_remaining);
997
998 if (reuse_packfile) {
871 - off_t packfile_size;
999 assert(pack_to_stdout);
873 -
874 - packfile_size = write_reused_pack(f);
875 - offset += packfile_size;
1000 + write_reused_pack(f);
1001 + offset = hashfile_total(f);
1002 }
1003
1004 nr_written = 0;
@@ -2677,6 +2803,7 @@ static void prepare_pack(int window, int depth)
2803
2804 if (nr_deltas && n > 1) {
2805 unsigned nr_done = 0;
2806 +
2807 if (progress)
2808 progress_state = start_progress(_("Compressing objects"),
2809 nr_deltas);
@@ -3062,7 +3189,6 @@ static int pack_options_allow_reuse(void)
3189 {
3190 return allow_pack_reuse &&
3191 pack_to_stdout &&
3065 - allow_ofs_delta &&
3192 !ignore_packed_keep_on_disk &&
3193 !ignore_packed_keep_in_core &&
3194 (!local || !have_non_local_packs) &&
@@ -3079,7 +3205,7 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
3205 bitmap_git,
3206 &reuse_packfile,
3207 &reuse_packfile_objects,
3082 - &reuse_packfile_offset)) {
3208 + &reuse_packfile_bitmap)) {
3209 assert(reuse_packfile_objects);
3210 nr_result += reuse_packfile_objects;
3211 display_progress(progress_state, nr_result);
pack-bitmap.c
+109 -41
@@ -326,6 +326,13 @@ failed:
326 munmap(bitmap_git->map, bitmap_git->map_size);
327 bitmap_git->map = NULL;
328 bitmap_git->map_size = 0;
329 +
330 + kh_destroy_oid_map(bitmap_git->bitmaps);
331 + bitmap_git->bitmaps = NULL;
332 +
333 + kh_destroy_oid_pos(bitmap_git->ext_index.positions);
334 + bitmap_git->ext_index.positions = NULL;
335 +
336 return -1;
337 }
338
@@ -770,65 +777,126 @@ cleanup:
777 return NULL;
778 }
779
773 -int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
774 - struct packed_git **packfile,
775 - uint32_t *entries,
776 - off_t *up_to)
780 +static void try_partial_reuse(struct bitmap_index *bitmap_git,
781 + size_t pos,
782 + struct bitmap *reuse,
783 + struct pack_window **w_curs)
784 {
785 + struct revindex_entry *revidx;
786 + off_t offset;
787 + enum object_type type;
788 + unsigned long size;
789 +
790 + if (pos >= bitmap_git->pack->num_objects)
791 + return; /* not actually in the pack */
792 +
793 + revidx = &bitmap_git->pack->revindex[pos];
794 + offset = revidx->offset;
795 + type = unpack_object_header(bitmap_git->pack, w_curs, &offset, &size);
796 + if (type < 0)
797 + return; /* broken packfile, punt */
798 +
799 + if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) {
800 + off_t base_offset;
801 + int base_pos;
802 +
803 + /*
804 + * Find the position of the base object so we can look it up
805 + * in our bitmaps. If we can't come up with an offset, or if
806 + * that offset is not in the revidx, the pack is corrupt.
807 + * There's nothing we can do, so just punt on this object,
808 + * and the normal slow path will complain about it in
809 + * more detail.
810 + */
811 + base_offset = get_delta_base(bitmap_git->pack, w_curs,
812 + &offset, type, revidx->offset);
813 + if (!base_offset)
814 + return;
815 + base_pos = find_revindex_position(bitmap_git->pack, base_offset);
816 + if (base_pos < 0)
817 + return;
818 +
819 + /*
820 + * We assume delta dependencies always point backwards. This
821 + * lets us do a single pass, and is basically always true
822 + * due to the way OFS_DELTAs work. You would not typically
823 + * find REF_DELTA in a bitmapped pack, since we only bitmap
824 + * packs we write fresh, and OFS_DELTA is the default). But
825 + * let's double check to make sure the pack wasn't written with
826 + * odd parameters.
827 + */
828 + if (base_pos >= pos)
829 + return;
830 +
831 + /*
832 + * And finally, if we're not sending the base as part of our
833 + * reuse chunk, then don't send this object either. The base
834 + * would come after us, along with other objects not
835 + * necessarily in the pack, which means we'd need to convert
836 + * to REF_DELTA on the fly. Better to just let the normal
837 + * object_entry code path handle it.
838 + */
839 + if (!bitmap_get(reuse, base_pos))
840 + return;
841 + }
842 +
843 /*
779 - * Reuse the packfile content if we need more than
780 - * 90% of its objects
844 + * If we got here, then the object is OK to reuse. Mark it.
845 */
782 - static const double REUSE_PERCENT = 0.9;
846 + bitmap_set(reuse, pos);
847 +}
848
849 +int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
850 + struct packed_git **packfile_out,
851 + uint32_t *entries,
852 + struct bitmap **reuse_out)
853 +{
854 struct bitmap *result = bitmap_git->result;
785 - uint32_t reuse_threshold;
786 - uint32_t i, reuse_objects = 0;
855 + struct bitmap *reuse;
856 + struct pack_window *w_curs = NULL;
857 + size_t i = 0;
858 + uint32_t offset;
859
860 assert(result);
861
790 - for (i = 0; i < result->word_alloc; ++i) {
791 - if (result->words[i] != (eword_t)~0) {
792 - reuse_objects += ewah_bit_ctz64(~result->words[i]);
793 - break;
794 - }
862 + while (i < result->word_alloc && result->words[i] == (eword_t)~0)
863 + i++;
864
796 - reuse_objects += BITS_IN_EWORD;
797 - }
865 + /* Don't mark objects not in the packfile */
866 + if (i > bitmap_git->pack->num_objects / BITS_IN_EWORD)
867 + i = bitmap_git->pack->num_objects / BITS_IN_EWORD;
868
799 -#ifdef GIT_BITMAP_DEBUG
800 - {
801 - const unsigned char *sha1;
802 - struct revindex_entry *entry;
869 + reuse = bitmap_word_alloc(i);
870 + memset(reuse->words, 0xFF, i * sizeof(eword_t));
871
804 - entry = &bitmap_git->reverse_index->revindex[reuse_objects];
805 - sha1 = nth_packed_object_sha1(bitmap_git->pack, entry->nr);
806 -
807 - fprintf(stderr, "Failed to reuse at %d (%016llx)\n",
808 - reuse_objects, result->words[i]);
809 - fprintf(stderr, " %s\n", hash_to_hex(sha1));
810 - }
811 -#endif
872 + for (; i < result->word_alloc; ++i) {
873 + eword_t word = result->words[i];
874 + size_t pos = (i * BITS_IN_EWORD);
875
813 - if (!reuse_objects)
814 - return -1;
876 + for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
877 + if ((word >> offset) == 0)
878 + break;
879
816 - if (reuse_objects >= bitmap_git->pack->num_objects) {
817 - bitmap_git->reuse_objects = *entries = bitmap_git->pack->num_objects;
818 - *up_to = -1; /* reuse the full pack */
819 - *packfile = bitmap_git->pack;
820 - return 0;
880 + offset += ewah_bit_ctz64(word >> offset);
881 + try_partial_reuse(bitmap_git, pos + offset, reuse, &w_curs);
882 + }
883 }
884
823 - reuse_threshold = bitmap_popcount(bitmap_git->result) * REUSE_PERCENT;
885 + unuse_pack(&w_curs);
886
825 - if (reuse_objects < reuse_threshold)
887 + *entries = bitmap_popcount(reuse);
888 + if (!*entries) {
889 + bitmap_free(reuse);
890 return -1;
891 + }
892
828 - bitmap_git->reuse_objects = *entries = reuse_objects;
829 - *up_to = bitmap_git->pack->revindex[reuse_objects].offset;
830 - *packfile = bitmap_git->pack;
831 -
893 + /*
894 + * Drop any reused objects from the result, since they will not
895 + * need to be handled separately.
896 + */
897 + bitmap_and_not(result, reuse);
898 + *packfile_out = bitmap_git->pack;
899 + *reuse_out = reuse;
900 return 0;
901 }
902
pack-bitmap.h
+2 -1
@@ -50,7 +50,8 @@ void test_bitmap_walk(struct rev_info *revs);
50 struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs);
51 int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
52 struct packed_git **packfile,
53 - uint32_t *entries, off_t *up_to);
53 + uint32_t *entries,
54 + struct bitmap **reuse_out);
55 int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping,
56 kh_oid_map_t *reused_bitmaps, int show_progress);
57 void free_bitmap_index(struct bitmap_index *);