Reduce pack-objects memory footprint?
linux-2.6.git current has 6483999 objects. "git gc" on my poor laptop consumes 1.7G out of 4G RAM, pushing lots of data to swap and making all apps nearly unusuable (granted the problem is partly Linux I/O scheduler too). So I wonder if we can reduce pack-objects memory footprint a bit.
This demonstration patch (probably breaks some tests) would reduce the size of struct object_entry from from 136 down to 112 bytes on x86-64. There are 6483999 of these objects, so the saving is 17% or 148 MB.
If we go further, notice that nr_objects is uint32_t, we could convert the three pointers
struct object_entry *delta; struct object_entry *delta_child; struct object_entry *delta_sibling;
to
uint32_t delta; uint32_t delta_child; uint32_t delta_sibling;
which saves 12 bytes (or another 74 MB). 222 MB total is plenty of space to keep some file cache from being evicted.
Is it worth doing this? The struct packing makes it harder to read (and more fragile too). I added some more artifical limit like max depth of 2^11. But I think 4096+ depth is getting unreasonable.
-- 8< --
builtin/pack-objects.c +11 -4
@@ -877,8 +877,11 @@ static void write_pack_file(void)strbuf_addf (&tmpname,"%s-" , base_name);if (write_bitmap_index) { +ALLOC_ARRAY (to_pack.in_pack_pos, to_pack.nr_objects);bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index (written_list, + nr_written, + &to_pack); } finish_tmp_packfile(&tmpname, pack_tmp_name,
@@ -1407,6 +1410,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,8 +1419,9 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size); + entry->in_pack_type = type;if (used ==0 ) goto give_up;
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
@@ -1580,7 +1586,8 @@ static void drop_reused_delta(struct object_entry *entry)*/ entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); - } + }else + entry->type = type; }/*
pack-bitmap-write.c +4 -3
@@ -49,7 +49,8 @@ void bitmap_writer_show_progress(int show)* Build the initial type index for the packfile */ void bitmap_writer_build_type_index (struct pack_idx_entry **index, -uint32_t index_nr) +uint32_t index_nr, +struct packing_data *to_pack) {uint32_t i;
@@ -62,7 +63,7 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; + to_pack->in_pack_pos[entry - to_pack->objects] = i;switch (entry->type) {case OBJ_COMMIT :
@@ -147,7 +148,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return writer.to_pack->in_pack_pos[entry - writer.to_pack->objects]; }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] = mapping->in_pack_pos[oe - mapping->objects] +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct pack_idx_entry **index, +uint32_t index_nr, +struct packing_data *);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +18 -16
@@ -1,6 +1,18 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* + * State flags for depth-first search used for analyzing delta cycles. + * + * The depth is measured in delta-links to the base (so if A is a delta + * against B, then A has a depth of 1, and B a depth of 0). + */ +enum dfs_state { + DFS_NONE = 0, + DFS_ACTIVE, + DFS_DONE +}; + struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
@@ -14,11 +26,10 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:3; /* enum object_type */ + unsigned in_pack_type:3; /* enum object_type - could be delta */ unsigned preferred_base:1; /** we do not pack this, but is available * to be used as the base object to delta
@@ -27,19 +38,8 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ - -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state; -int depth; + unsigned dfs_state:3; /* enum dfs_state */ + unsigned depth:11; };struct packing_data {
@@ -48,6 +48,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
-- 8< --
Quoted 5 lines · Duy Nguyen
linux-2.6.git current has 6483999 objects. "git gc" on my poor laptop consumes 1.7G out of 4G RAM, pushing lots of data to swap and making all apps nearly unusuable (granted the problem is partly Linux I/O scheduler too). So I wonder if we can reduce pack-objects memory footprint a bit.
Yeah, the per object memory footprint is not great. Around 100 million objects it becomes pretty ridiculous. I started to dig into it a year or three ago when I saw such a case, but it turned out to be something that we could prune. The torvalds/linux fork network has ~23 million objects, so it's probably 7-8 GB of book-keeping. Which is gross, but 64GB in a server isn't uncommon these days.
I think laptops repacking the kernel are probably one of the worst cases (leaving aside the awful Windows repository, but my impression is that they simply can't do a full repack at all there).
Quoted 4 lines
This demonstration patch (probably breaks some tests) would reduce the size of struct
object_entryfrom from 136 down to 112 bytes on x86-64. There are 6483999 of these objects, so the saving is 17% or 148 MB.
136 x 6.5M objects is only about 800MB. I suspect a big chunk of the rest is going to the object structs we create when doing the internal rev-list traversal. And those duplicate the 20-byte sha1s at the very least.
I don't know if it would be a good idea to free them after the traversal, though. We do use them again later in the bitmap case. On the other hand, we could probably do so for the non-bitmap case. And even for the bitmap case, the primary value in keeping them around is that the parent pointers will already be cached. So it might make sense to free the blobs and trees (though it might be tricky; the commits have pointers to the trees).
It also doesn't help with peak memory usage, because you'll have the full to_pack list and all of the "struct object" in memory together at one point.
Another option would be to somehow replace the pack_idx_entry with a reference to a "struct object". That would let us at least avoid storing the 20-byte oid twice.
Quoted 7 lines
If we go further, notice that
nr_objectsisuint32_t, we could convert the three pointersstruct object_entry *delta; struct object_entry *delta_child; struct object_entry *delta_sibling;
Quoted 8 lines
to
uint32_t delta; uint32_t delta_child; uint32_t delta_sibling;which saves 12 bytes (or another 74 MB). 222 MB total is plenty of space to keep some file cache from being evicted.
Yeah, that seems like low-hanging fruit. I'd also note that we don't actually use all of the fields during the whole process. I think some of those delta fields are only used for a short time. So we might be able to reduce peak memory if there are some mutually exclusive bits of each entry (and even if there's some overlap, we'd reduce the length of time we'd need to be at peak).
Quoted 3 lines
Is it worth doing this? The struct packing makes it harder to read (and more fragile too). I added some more artifical limit like max depth of 2^11. But I think 4096+ depth is getting unreasonable.
I'm OK with a limit like 4096, as long as we notice when we hit the limit and behave reasonably. I think the algorithm in break_delta_chains() may temporarily store up to uint32_t depth. But I think we won't ever write anything into cur->depth larger than the max-depth limit. So it would probably be sufficient to just check that the --depth argument is reasonably sized and complain otherwise.
I do agree this makes things a bit harder to read, but I think the benefits are pretty measurable. And may make a real usability difference on a large repository.
-Peff
Quoted 8 lines · Jeff King
Quoted 5 lines · Duy Nguyen
linux-2.6.git current has 6483999 objects. "git gc" on my poor laptop consumes 1.7G out of 4G RAM, pushing lots of data to swap and making all apps nearly unusuable (granted the problem is partly Linux I/O scheduler too). So I wonder if we can reduce pack-objects memory footprint a bit.
Quoted 4 lines
Yeah, the per object memory footprint is not great. Around 100 million objects it becomes pretty ridiculous. I started to dig into it a year or three ago when I saw such a case, but it turned out to be something that we could prune.
We could? What could we prune?
Quoted 3 lines
The torvalds/linux fork network has ~23 million objects,
so it's probably 7-8 GB of book-keeping. Which is gross, but 64GB in a server isn't uncommon these days.
I wonder if we could just do book keeping for some but not all objects because all objects simply do not scale. Say we have a big pack of many GBs, could we keep the 80% of its bottom untouched, register the top 20% (mostly non-blobs, and some more blobs as delta base) for repack? We copy the bottom part to the new pack byte-by-byte, then pack-objects rebuilds the top part with objects from other sources.
That would of course be less optimal because we can't make delta against those objects at the bottom. And this makes the assumption that these packs are generated using the same heuristics that we use.
Quoted 3 lines
I think laptops repacking the kernel are probably one of the worst cases (leaving aside the awful Windows repository, but my impression is that they simply can't do a full repack at all there).
Yeah. My fear is "git gc --auto" kicking in. Even on a laptop we should support working on a repo as big as linux-2.6 (or as smalls as one from facebook/microsoft perspective). I probably will add a mode that keeps the largest pack alone and just pack the rest in a second pack in "git gc --auto". That might help (I haven't really checked the details yet, but object_entry book keeping should go down at least)
Quoted 9 lines
Quoted 4 lines
This demonstration patch (probably breaks some tests) would reduce the size of struct
object_entryfrom from 136 down to 112 bytes on x86-64. There are 6483999 of these objects, so the saving is 17% or 148 MB.136 x 6.5M objects is only about 800MB. I suspect a big chunk of the rest is going to the object structs we create when doing the internal rev-list traversal. And those duplicate the 20-byte sha1s at the very least.
They are 32 bytes per entry, so it should take less than object_entry. I briefly wondered if we should fall back to external rev-list too, just to free that memory.
So about 200 MB for those objects (or maybe more for commits). Add 256 MB delta cache on top, it's still a bit far from 1.7G. There's something I'm still missing.
Quoted 3 lines
Another option would be to somehow replace the
pack_idx_entrywith a reference to a "struct object". That would let us at least avoid storing the 20-byte oid twice.
20 bytes saving? /me drools.
Pity we can't do the same for 'struct object'. Most of the time we have a giant .idx file with most hashes. We could look up in both
places: the hash table in object.c, and the idx file, to find an object. Then those objects that are associated with .idx file will not need "oid" field (needed to as key for the hash table). But I see no way to make that change.
Quoted 16 lines
Quoted 7 lines
If we go further, notice that
nr_objectsisuint32_t, we could convert the three pointersstruct object_entry *delta; struct object_entry *delta_child; struct object_entry *delta_sibling;Quoted 8 lines
to
uint32_t delta; uint32_t delta_child; uint32_t delta_sibling;which saves 12 bytes (or another 74 MB). 222 MB total is plenty of space to keep some file cache from being evicted.
Quoted 6 lines
Yeah, that seems like low-hanging fruit. I'd also note that we don't actually use all of the fields during the whole process. I think some of those delta fields are only used for a short time. So we might be able to reduce peak memory if there are some mutually exclusive bits of each entry (and even if there's some overlap, we'd reduce the length of time we'd need to be at peak).
Yeah it looks that way to me too. Now that we have packing_data, splitting object_entry[] to multiple arrays (that is only needed at some point) may be very nice.
Quoted 11 lines
Quoted 3 lines
Is it worth doing this? The struct packing makes it harder to read (and more fragile too). I added some more artifical limit like max depth of 2^11. But I think 4096+ depth is getting unreasonable.
I'm OK with a limit like 4096, as long as we notice when we hit the limit and behave reasonably. I think the algorithm in
break_delta_chains()may temporarily store up touint32_tdepth. But I think we won't ever write anything intocur->depthlarger than the max-depth limit. So it would probably be sufficient to just check that the --depth argument is reasonably sized and complain otherwise.
Quoted 3 lines
I do agree this makes things a bit harder to read, but I think the benefits are pretty measurable. And may make a real usability difference on a large repository.
Thanks. I'll go make real patches then (with lots more checks to make sure we don't overflow in shortened fields like depth or type).
Signature · 1 line
Duy
Quoted 6 lines · Duy Nguyen
Quoted 4 lines
Yeah, the per object memory footprint is not great. Around 100 million objects it becomes pretty ridiculous. I started to dig into it a year or three ago when I saw such a case, but it turned out to be something that we could prune.
We could? What could we prune?
Sorry, I just meant that my 100 million-object case turned out not to need all those objects, and I was able to prune it down. No code fixes came out of it. ;)
Quoted 10 lines
Quoted 3 lines
The torvalds/linux fork network has ~23 million objects,
so it's probably 7-8 GB of book-keeping. Which is gross, but 64GB in a server isn't uncommon these days.I wonder if we could just do book keeping for some but not all objects because all objects simply do not scale. Say we have a big pack of many GBs, could we keep the 80% of its bottom untouched, register the top 20% (mostly non-blobs, and some more blobs as delta base) for repack? We copy the bottom part to the new pack byte-by-byte, then pack-objects rebuilds the top part with objects from other sources.
Yes, though I think it would take a fair bit of surgery to do internally. And some features (like bitmap generation) just wouldn't work at all.
I suspect you could simulate it, though, by just packing your subset with pack-objects (feeding it directly without using "--revs") and then catting the resulting packfiles together with a fixed-up header.
At one point I played with a "fast pack" that would just cat packfiles together. My goal was to make cases with 10,000 packs workable by creating one lousy pack, and then repacking that lousy pack with a "real" repack. In the end I abandoned it in favor of fixing the performance problems from trying to make a real pack of 10,000 packs. :)
But I might be able to dig it up if you want to experiment in that direction.
Quoted 7 lines
They are 32 bytes per entry, so it should take less than
object_entry. I briefly wondered if we should fall back to external rev-list too, just to free that memory.So about 200 MB for those objects (or maybe more for commits). Add 256 MB delta cache on top, it's still a bit far from 1.7G. There's something I'm still missing.
Are you looking at RSS or heap? Keep in mind that you're mmap-ing what's probably a 1GB packfile on disk. If you're under memory pressure that won't all stay resident, but some of it will be counted in RSS.
Quoted 6 lines
Pity we can't do the same for 'struct object'. Most of the time we have a giant .idx file with most hashes. We could look up in both
places: the hash table inobject.c, and the idx file, to find an object. Then those objects that are associated with .idx file will not need "oid" field (needed to as key for the hash table). But I see no way to make that change.
Yeah, that would be pretty invasive, I think. I also wonder if it would perform worse due to cache effects.
-Peff
Quoted 19 lines · Jeff King
Quoted 10 lines
Quoted 3 lines
The torvalds/linux fork network has ~23 million objects,
so it's probably 7-8 GB of book-keeping. Which is gross, but 64GB in a server isn't uncommon these days.I wonder if we could just do book keeping for some but not all objects because all objects simply do not scale. Say we have a big pack of many GBs, could we keep the 80% of its bottom untouched, register the top 20% (mostly non-blobs, and some more blobs as delta base) for repack? We copy the bottom part to the new pack byte-by-byte, then pack-objects rebuilds the top part with objects from other sources.
Yes, though I think it would take a fair bit of surgery to do internally. And some features (like bitmap generation) just wouldn't work at all.
I suspect you could simulate it, though, by just packing your subset with pack-objects (feeding it directly without using "--revs") and then catting the resulting packfiles together with a fixed-up header.
Quoted 8 lines
At one point I played with a "fast pack" that would just cat packfiles together. My goal was to make cases with 10,000 packs workable by creating one lousy pack, and then repacking that lousy pack with a "real" repack. In the end I abandoned it in favor of fixing the performance problems from trying to make a real pack of 10,000 packs. :)
But I might be able to dig it up if you want to experiment in that direction.
Naah it's ok. I'll go similar direction, but I'd repack those pack files too except the big one. Let's see how it turns out.
Quoted 11 lines
Quoted 7 lines
They are 32 bytes per entry, so it should take less than
object_entry. I briefly wondered if we should fall back to external rev-list too, just to free that memory.So about 200 MB for those objects (or maybe more for commits). Add 256 MB delta cache on top, it's still a bit far from 1.7G. There's something I'm still missing.
Are you looking at RSS or heap? Keep in mind that you're mmap-ing what's probably a 1GB packfile on disk. If you're under memory pressure that won't all stay resident, but some of it will be counted in RSS.
Interesting. It was RSS.
Quoted 9 lines
Quoted 6 lines
Pity we can't do the same for 'struct object'. Most of the time we have a giant .idx file with most hashes. We could look up in both
places: the hash table inobject.c, and the idx file, to find an object. Then those objects that are associated with .idx file will not need "oid" field (needed to as key for the hash table). But I see no way to make that change.Yeah, that would be pretty invasive, I think. I also wonder if it would perform worse due to cache effects.
It should be better because of cache effects, I think. I mean, hash map is the least cache friendly lookup. Moving most objects out of the hash table shrinks it, which is even nicer to cache. But we also lose O(1) when we do binary search on .idx file (after failing to find the same object in the hash table)
Signature · 1 line
Duy
Quoted 2 lines · Duy Nguyen
which saves 12 bytes (or another 74 MB). 222 MB total is plenty of space to keep some file cache from being evicted.
Nice! I can definitely benefit from lower memory usage when packing. Fwiw, I use pahole with other projects to help find packing opportunities:
git://git.kernel.org/pub/scm/devel/pahole/pahole.git
pack-objects.h struct object_entry { · line 32
Quoted 11 lines
pack-objects.h +2
-3@@ -14,11 +26,10 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:3; /* enum object_type */ + unsigned in_pack_type:3; /* enum object_type - could be delta */
For C99 compilers, enums can be bitfields. I introduced the following macro into Ruby a few weeks ago to remain compatible with non-C99 compilers:
/*
* For declaring bitfields out of non-unsigned int types:
* struct date {
* BITFIELD(enum months) month:4;
* ...
* };
*/
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define BITFIELD(type) type #else # define BITFIELD(type) unsigned int #endif
Quoted 9 lines · Eric Wong
Quoted 2 lines · Duy Nguyen
which saves 12 bytes (or another 74 MB). 222 MB total is plenty of space to keep some file cache from being evicted.
Nice! I can definitely benefit from lower memory usage when packing. Fwiw, I use pahole with other projects to help find packing opportunities:
git://git.kernel.org/pub/scm/devel/pahole/pahole.git
Yes it's a wonderful tool.
Quoted 24 lines
pack-objects.h struct object_entry { · line 32
Quoted 11 lines
pack-objects.h +2
-3@@ -14,11 +26,10 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:3; /* enum object_type */ + unsigned in_pack_type:3; /* enum object_type - could be delta */For C99 compilers, enums can be bitfields. I introduced the following macro into Ruby a few weeks ago to remain compatible with non-C99 compilers:
/*
* For declaring bitfields out of non-unsigned int types:
* struct date {
* BITFIELD(enum months) month:4;
* ...
* };
*/
#ifdefined(__STDC_VERSION__)&& (__STDC_VERSION__>= 199901L)
Quoted 4 lines
# define BITFIELD(type) type #else # define BITFIELD(type) unsigned int #endif
I tried this and got
In file included from builtin/pack-objects.c:20:0:
./pack-objects.h:49:19: l?i: ?type? is narrower than values of its type [-Werror]
enum object_type type:TYPE_BITS;
^~~~The compiler is not wrong. What it does not realize is pack-objects code never uses out-of-range values (OBJ_BAD and OBJ_ANY) but I don't see how I could suppress this warning. So I went back to non-enum bitfields.
--
Duy
cover Reduce pack-objects memory footprint
In reply to Duy NguyenThe array of object_entry in pack-objects can take a lot of memory when pack-objects is run in "pack everything" mode. On linux-2.6.git, this array alone takes roughly 800MB.
This series reorders some fields and reduces field size... to keep this struct smaller. Its size goes from 136 bytes to 96 bytes (29%) on 64-bit linux and saves 260MB on linux-2.6.git.
Now the bad side:
- the number of pack files pack-objects can handle is reduced to 4096 (previously unlimited)
- max delta chain is also limited to 4096 (previously practically unlimited)
- some patches are quite invasive (e.g. replacing pointer with uint32_t) and reduces readability a bit.
- it may be tricker to add more data in object_entry in the future.
Nguyễn Thái Ngọc Duy (11):
pack-objects: document holes in struct object_entry.h
pack-objects: turn type and in_pack_type to bitfields
pack-objects: use bitfield for object_entry::dfs_state
pack-objects: use bitfield for object_entry::depth
pack-objects: note about in_pack_header_size
pack-objects: move in_pack_pos out of struct object_entry
pack-objects: move in_pack out of struct object_entry
pack-objects: faster reverse packed_git lookup
pack-objects: refer to delta objects by index instead of pointer
pack-objects: reorder 'hash' to pack struct object_entry
pack-objects: increase pack file limit to 4096
builtin/pack-objects.c | 189 ++++++++++++++++++++++++++--------------- cache.h | 3 + object.h | 1 - pack-bitmap-write.c | 8 +- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +- pack-objects.h | 70 ++++++++++----- 7 files changed, 180 insertions(+), 97 deletions(-)
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 1/11 pack-objects: document holes in struct object_entry.h
--- pack-objects.h | 2 ++ 1 file changed, 2 insertions(+)
pack-objects.h +2 -0
@@ -28,6 +28,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ +/* XXX 28 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -40,6 +41,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; +/* size: 136, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 2/11 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyThis saves 8 bytes in sizeof(struct object_entry). On a large repository like linux-2.6.git (6.5M objects), this saves us 52MB memory.
--- builtin/pack-objects.c | 14 ++++++++++++-- cache.h | 2 ++ object.h | 1 - pack-objects.h | 8 ++++---- 4 files changed, 18 insertions(+), 7 deletions(-)
builtin/pack-objects.c +12 -2
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
@@ -1580,6 +1586,10 @@ static void drop_reused_delta(struct object_entry *entry)*/ entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); + }else { +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->type = type; } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-objects.h +4 -4
@@ -14,11 +14,11 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /** we do not pack this, but is available * to be used as the base object to delta
@@ -28,7 +28,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ -/* XXX 28 bits hole, try to pack */ +/* XXX 22 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -41,7 +41,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; -/* size: 136, padding: 4 */ +/* size: 128, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 3/11 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +20 -13
@@ -1,6 +1,21 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* + * State flags for depth-first search used for analyzing delta cycles. + * + * The depth is measured in delta-links to the base (so if A is a delta + * against B, then A has a depth of 1, and B a depth of 0). + */ +enum dfs_state { + DFS_NONE = 0, + DFS_ACTIVE, + DFS_DONE, + DFS_NUM_STATES +}; + struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
@@ -27,21 +42,13 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; + +/* XXX 20 bits hole, try to pack */ -/* XXX 22 bits hole, try to pack */ -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; -/* size: 128, padding: 4 */ + +/* size: 120, padding: 4 */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 4/11 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyThis does not give us any saving due to padding. But we will be able to save once we cut 4 bytes out of this struct in a subsequent patch.
Because of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- builtin/pack-objects.c | 4 ++++ pack-objects.h | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-)
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth > (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +2 -4
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -43,10 +44,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; - -/* XXX 20 bits hole, try to pack */ - -int depth; + unsigned depth:OE_DEPTH_BITS;/* size: 120, padding: 4 */ };
Signature · 1 line
2.16.1.435.g8f24da2e1a
Re: [PATCH 04/11] pack-objects: use bitfield for object_entry::depth
Quoted 10 lines · Nguyễn Thái Ngọc Duy
This does not give us any saving due to padding. But we will be able to save once we cut 4 bytes out of this struct in a subsequent patch.
Because of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
This should be marked as RFC/PATCH; I do not have objection to limiting the delta depth to some reasonable length (rather than the current 1<<31 or worse 1<<63), and 4k may be such a reasonable limit (I'd actually think anything more than a few hundreds is probably a bad idea), but it needs to be documented.
Quoted 43 lines
builtin/pack-objects.c | 4 ++++ pack-objects.h | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-)builtin/pack-objects.c +4
-0@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth > (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;pack-objects.h +2
-4@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles. @@ -43,10 +44,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; - -/* XXX 20 bits hole, try to pack */ - -int depth; + unsigned depth:OE_DEPTH_BITS;/* size: 120, padding: 4 */ };
PATCH 5/11 pack-objects: note about in_pack_header_size
In reply to Nguyễn Thái Ngọc DuyObject header in a pack is packed really tight (see pack-format.txt). Even with 8 bytes length, we need 9-10 bytes most, plus a hash (20 bytes). Which means this field only needs to store a number as big as 32 (5 bits).
This is trickier to pack tight though since a new hash algorithm is coming, the number of bits needed may quickly increase. So leave it for now.
--- pack-objects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
pack-objects.h +1 -1
@@ -32,7 +32,7 @@ struct object_entry {unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos; -unsigned char in_pack_header_size; +unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /*
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 6/11 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not). This saves us 8 bytes in struct object_entry.
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 8 ++++++-- 5 files changed, 17 insertions(+), 8 deletions(-)
builtin/pack-objects.c +2 -1
@@ -878,7 +878,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +IN_PACK_POS (to_pack, entry) = i;switch (entry->type) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return IN_PACK_POS (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =IN_PACK_POS (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +6 -2
@@ -4,6 +4,9 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 +#define IN_PACK_POS (to_pack, obj) \ + (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects] +/* * State flags for depth-first search used for analyzing delta cycles. *
@@ -31,7 +34,6 @@ struct object_entry {unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */
@@ -46,7 +48,7 @@ struct object_entry { unsigned dfs_state:OE_DFS_STATE_BITS; unsigned depth:OE_DEPTH_BITS; -/* size: 120, padding: 4 */ +/* size: 112 */ };struct packing_data {
@@ -55,6 +57,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 7/11 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 256 (still unreasonably high for a repo to work well). If you have more than 256 packs, you'll need an older version of Git to repack first.
This technically saves 7 bytes. But we don't see any of that in practice due to padding. The saving becomes real when we pack this struct tighter later.
--- builtin/pack-objects.c | 48 ++++++++++++++++++++++++++++++++---------- pack-objects.h | 12 +++++++++-- 2 files changed, 47 insertions(+), 13 deletions(-)
builtin/pack-objects.c +37 -11
@@ -367,7 +367,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p = entry->in_pack; +struct packed_git *p =IN_PACK (&to_pack, entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -478,7 +478,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!entry->in_pack) +else if (!IN_PACK (&to_pack, entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (entry->type ==OBJ_REF_DELTA || entry->type ==OBJ_OFS_DELTA )/* check_object() decided it for us ... */
@@ -1074,7 +1074,15 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { - entry->in_pack = found_pack; +int i; + +for (i =0 ; i < (1 <<OE_IN_PACK_BITS ); i++) +if (to_pack.in_pack[i] == found_pack) { + entry->in_pack_idx = i; +break ; + } +if (i == (1 <<OE_IN_PACK_BITS )) +die ("BUG: pack not found!" ); entry->in_pack_offset = found_offset; }
@@ -1399,8 +1407,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (entry->in_pack) { -struct packed_git *p = entry->in_pack; +if (IN_PACK (&to_pack, entry)) { +struct packed_git *p =IN_PACK (&to_pack, entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1535,14 +1543,16 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +const struct packed_git *a_in_pack =IN_PACK (&to_pack, a); +const struct packed_git *b_in_pack =IN_PACK (&to_pack, b);/* avoid filesystem trashing with loose objects */ -if (!a->in_pack && !b->in_pack) +if (!a_in_pack && !b_in_pack)return oidcmp (&a->idx.oid, &b->idx.oid); -if (a->in_pack < b->in_pack) +if (a_in_pack < b_in_pack)return -1 ; -if (a->in_pack > b->in_pack) +if (a_in_pack > b_in_pack)return 1 ;return a->in_pack_offset < b->in_pack_offset ?-1 : (a->in_pack_offset > b->in_pack_offset);
@@ -1578,7 +1588,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (&to_pack, entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1848,8 +1858,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && +if (reuse_delta &&IN_PACK (&to_pack, trg_entry) && +IN_PACK (&to_pack, trg_entry) ==IN_PACK (&to_pack, src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -2958,6 +2968,21 @@ static int option_parse_unpack_unreachable(const struct option *opt,return 0 ; } +static void init_in_pack_mapping (struct packing_data *to_pack) +{ +struct packed_git *p; +int i =0 ; + +/* let IN_PACK() return NULL if in_pack_idx is zero */ + to_pack->in_pack[i++] =NULL ; + +for (p = packed_git; p; p = p->next, i++) { +if (i >= (1 <<OE_IN_PACK_BITS )) +die ("BUG: too many packs to handle!" ); + to_pack->in_pack[i] = p; + } +} +int cmd_pack_objects (int argc,const char **argv,const char *prefix) {int use_internal_rev_list =0 ;
@@ -3190,6 +3215,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } } +init_in_pack_mapping (&to_pack);if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );
pack-objects.h +10 -2
@@ -3,10 +3,14 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 +#define OE_IN_PACK_BITS 8#define IN_PACK_POS (to_pack, obj) \ (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects] +#define IN_PACK (to_pack, obj) \ + (to_pack)->in_pack[(obj)->in_pack_idx] +/* * State flags for depth-first search used for analyzing delta cycles. *
@@ -23,7 +27,6 @@ enum dfs_state {struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */ -struct packed_git *in_pack;/* already in pack */ off_t in_pack_offset;struct object_entry *delta;/* delta base object */ struct object_entry *delta_child;/* deltified objects who bases me */
@@ -35,6 +38,7 @@ struct object_entry {unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ unsigned char in_pack_header_size;/* note: spare bits available! */ + unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /*
@@ -46,9 +50,12 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; + +/* XXX 12 bits hole, try to pack */ + unsigned depth:OE_DEPTH_BITS; -/* size: 112 */ +/* size: 112, padding: 4 */ };struct packing_data {
@@ -59,6 +66,7 @@ struct packing_data {uint32_t index_size;unsigned int *in_pack_pos; +struct packed_git *in_pack[1 <<OE_IN_PACK_BITS ]; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
Signature · 1 line
2.16.1.435.g8f24da2e1a
Re: [PATCH 07/11] pack-objects: move in_pack out of struct object_entry
On Thu, Mar 01 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 1 line
pack. Use an index isntead since the number of packs should be
s/isntead/instead/
Quoted 3 lines
This limits the number of packs we can handle to 256 (still unreasonably high for a repo to work well). If you have more than 256 packs, you'll need an older version of Git to repack first.
So if you have gc.autoPackLimit=300 this will break, how does it break?
Should we also make (& document) setting that variable higher than 256 an error?
Re: [PATCH 07/11] pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyQuoted 7 lines · Nguyễn Thái Ngọc Duy
Instead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 256 (still unreasonably high for a repo to work well). If you have more than 256 packs, you'll need an older version of Git to repack first.
I overall like the direction of this series, but I think this one is just too much. While you definitely shouldn't have a ton of packs, this leaves the user with no real escape hatch. And 256 isn't actually that many packs.
-Peff
Re: [PATCH 07/11] pack-objects: move in_pack out of struct object_entry
Quoted 6 lines · Jeff King
Quoted 4 lines · Nguyễn Thái Ngọc Duy
Instead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
Quoted 8 lines
Quoted 3 lines
This limits the number of packs we can handle to 256 (still unreasonably high for a repo to work well). If you have more than 256 packs, you'll need an older version of Git to repack first.
I overall like the direction of this series, but I think this one is just too much. While you definitely shouldn't have a ton of packs, this leaves the user with no real escape hatch. And 256 isn't actually that many packs.
It was raised back to 4096 at the end (I didn't know how many spare bits we had at this point).
Agreed on the escape hatch though. I think we could do better: if there are more than X packs, we repack X packs into one and leave the rest alone. The _next_ pack-objects will pick another X packs to combine. Repeat until you only have one pack left.
Signature · 1 line
Duy
Re: [PATCH 07/11] pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyQuoted 7 lines · Nguyễn Thái Ngọc Duy
Instead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 256 (still unreasonably high for a repo to work well). If you have more than 256 packs, you'll need an older version of Git to repack first.
I can tell without looking at the rest of the thread that people had reasonable objection against this stance ;-) This will not fly well.
PATCH 8/11 pack-objects: faster reverse packed_git lookup
In reply to Nguyễn Thái Ngọc DuyWe do a linear search for in_pack index in create_object_entry(). This function is called for every available object in the worst case (and on linux-2.6.git, that's about 6.5M). Try to avoid that by saving the index in packed_git. Since we should not have zillions of packs, this extra space should not be a big deal.
--- builtin/pack-objects.c | 11 ++--------- cache.h | 1 + 2 files changed, 3 insertions(+), 9 deletions(-)
builtin/pack-objects.c +2 -9
@@ -1074,15 +1074,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { -int i; - -for (i =0 ; i < (1 <<OE_IN_PACK_BITS ); i++) -if (to_pack.in_pack[i] == found_pack) { - entry->in_pack_idx = i; -break ; - } -if (i == (1 <<OE_IN_PACK_BITS )) -die ("BUG: pack not found!" ); + entry->in_pack_idx = found_pack->index; entry->in_pack_offset = found_offset; }
@@ -2980,6 +2972,7 @@ static void init_in_pack_mapping(struct packing_data *to_pack)if (i >= (1 <<OE_IN_PACK_BITS ))die ("BUG: too many packs to handle!" ); to_pack->in_pack[i] = p; + p->index = i; } }
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 9/11 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyNotice that packing_data::nr_objects is uint32_t, we could only handle maximum 4G objects and can address all of them with an uint32_t. If we use a pointer here, we waste 4 bytes on 64 bit architecture.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
There are holes in this struct but this patch is already big. Struct packing can be done separately. Even with holes, we save 8 bytes per object_entry.
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 124 +++++++++++++++++++++++------------------ pack-objects.h | 14 +++-- 2 files changed, 78 insertions(+), 60 deletions(-)
builtin/pack-objects.c +70 -54
@@ -29,6 +29,20 @@#include "list.h" #include "packfile.h" +#define DELTA (obj) \ + ((obj)->delta_idx ? &to_pack.objects[(obj)->delta_idx - 1] : NULL) +#define DELTA_CHILD (obj) \ + ((obj)->delta_child_idx ? &to_pack.objects[(obj)->delta_child_idx - 1] : NULL) +#define DELTA_SIBLING (obj) \ + ((obj)->delta_sibling_idx ? &to_pack.objects[(obj)->delta_sibling_idx - 1] : NULL) + +#define CLEAR_DELTA (obj) (obj)->delta_idx = 0 +#define CLEAR_DELTA_CHILD (obj) (obj)->delta_child_idx = 0 +#define CLEAR_DELTA_SIBLING (obj) (obj)->delta_sibling_idx = 0 + +#define SET_DELTA (obj, val) (obj)->delta_idx = ((val) - to_pack.objects) + 1 +#define SET_DELTA_CHILD (obj, val) (obj)->delta_child_idx = ((val) - to_pack.objects) + 1 +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -125,11 +139,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -286,12 +300,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -315,7 +329,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -341,7 +355,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -377,8 +391,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -406,7 +420,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -425,7 +439,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -465,13 +479,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -486,7 +500,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (entry->type != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -538,12 +552,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +CLEAR_DELTA (e);break ;default :break ;
@@ -605,34 +619,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -643,7 +657,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -658,8 +672,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +CLEAR_DELTA_CHILD (&objects[i]); +CLEAR_DELTA_SIBLING (&objects[i]); }/*
@@ -669,11 +683,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1491,10 +1505,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ entry->type = entry->in_pack_type; - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1565,17 +1579,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +CLEAR_DELTA (entry); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1617,7 +1633,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1642,7 +1658,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1665,7 +1681,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1680,7 +1696,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1863,7 +1879,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1939,7 +1955,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1968,7 +1984,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1977,13 +1993,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2052,7 +2068,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2102,7 +2118,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2110,7 +2126,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2431,7 +2447,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +8 -6
@@ -28,11 +28,13 @@ struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */ off_t in_pack_offset; -struct object_entry *delta;/* delta base object */ -struct object_entry *delta_child;/* deltified objects who bases me */ -struct object_entry *delta_sibling;/* other deltified objects who -* uses the same base as me -*/ +uint32_t delta_idx;/* delta base object */ +uint32_t delta_child_idx;/* deltified objects who bases me */ +uint32_t delta_sibling_idx;/* other deltified objects who +* uses the same base as me +*/ +/* XXX 4 bytes hole, try to pack */ +void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */
@@ -55,7 +57,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 112, padding: 4 */ +/* size: 104, padding: 4 */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
Re: [PATCH 09/11] pack-objects: refer to delta objects by index instead of pointer
Quoted 6 lines · Nguyễn Thái Ngọc Duy
Notice that
packing_data::nr_objectsisuint32_t, we could only handle maximum 4G objects and can address all of them with anuint32_t. If we use a pointer here, we waste 4 bytes on 64 bit architecture.Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
Makes perfect sense.
I do not think losing 1 slot out of possible 4G is a regression, unlike the 256 packfile limit 07/11 imposes.
PATCH 10/11 pack-objects: reorder 'hash' to pack struct object_entry
In reply to Nguyễn Thái Ngọc Duy--- pack-objects.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
pack-objects.h +2 -4
@@ -33,12 +33,10 @@ struct object_entry {uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -/* XXX 4 bytes hole, try to pack */ - +uint32_t hash;/* name hint hash */ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -uint32_t hash;/* name hint hash */ unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS;
@@ -57,7 +55,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 104, padding: 4 */ +/* size: 96 */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH 11/11 pack-objects: increase pack file limit to 4096
In reply to Nguyễn Thái Ngọc DuyWhen OE_IN_PACK_BITS was added, we didn't have many bits left to spare so the max number of packs that pack-objects could handle was limited to 256. Now we have more spare bits, let's increase it to 4096 to be on the safe side. If you have more than this many packs, you may need to reconsider if you're still sane.
Increasing this also increases memory a bit because in_pack[] array in packing_data is bigger, roughly 32kb, which is insignificant in pack-objects context.
--- pack-objects.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
pack-objects.h +7 -2
@@ -3,7 +3,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 -#define OE_IN_PACK_BITS 8 +#define OE_IN_PACK_BITS 12#define IN_PACK_POS (to_pack, obj) \ (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects]
@@ -24,6 +24,11 @@ enum dfs_state {DFS_NUM_STATES }; +/* +* The size of struct nearly determines pack-objects's memory +* consumption. This struct is packed tight for that reason. When you +* add or reorder something in this struct, think a bit about this. +*/ struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */
@@ -51,7 +56,7 @@ struct object_entry { unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; -/* XXX 12 bits hole, try to pack */ +/* XXX 8 bits hole, try to pack */ unsigned depth:OE_DEPTH_BITS;
Signature · 1 line
2.16.1.435.g8f24da2e1a
Re: [PATCH 00/11] Reduce pack-objects memory footprint
In reply to Nguyễn Thái Ngọc DuyOn Thu, Mar 01 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 7 lines
The array of
object_entryin pack-objects can take a lot of memory when pack-objects is run in "pack everything" mode. On linux-2.6.git, this array alone takes roughly 800MB.This series reorders some fields and reduces field size... to keep this struct smaller. Its size goes from 136 bytes to 96 bytes (29%) on 64-bit linux and saves 260MB on linux-2.6.git.
I'm very interested in this patch series. I don't have time to test this one right now (have to run), but with your previous RFC patch memory use (in the ~4GB range) on a big in-house repo went down by a bit over 3%, and it's ~5% faster.
Before/after RSS 4440812 / 4290000 & runtime 172.73 / 162.45. This is after having already done a full git gc before, data via /usr/bin/time -v.
So not huge, but respectable.
We have a big repo, and this gets repacked on 6-8GB of memory on dev KVMs, so we're under a fair bit of memory pressure. git-gc slows things down a lot.
It would be really nice to have something that made it use drastically less memory at the cost of less efficient packs. Is the property that you need to spend give or take the size of .git/objects in memory something inherent, or just a limitation of the current implementation? I.e. could we do a first pass to pick some objects based on some heuristic, then repack them N at a time, and finally delete the now-obsolete packs?
Another thing I've dealt with is that on these machines their NFS-mounted storage gets exhausted (I'm told) due to some pathological operations git does during repack, I/O tends to get 5-6x slower. Of course ionice doesn't help because the local kernel doesn't know anything about how harmful it is.
Re: [PATCH 00/11] Reduce pack-objects memory footprint
Quoted 11 lines · Ævar Arnfjörð Bjarmason
On Thu, Mar 01 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 7 lines
The array of
object_entryin pack-objects can take a lot of memory when pack-objects is run in "pack everything" mode. On linux-2.6.git, this array alone takes roughly 800MB.This series reorders some fields and reduces field size... to keep this struct smaller. Its size goes from 136 bytes to 96 bytes (29%) on 64-bit linux and saves 260MB on linux-2.6.git.
Quoted 8 lines
I'm very interested in this patch series. I don't have time to test this one right now (have to run), but with your previous RFC patch memory use (in the ~4GB range) on a big in-house repo went down by a bit over 3%, and it's ~5% faster.
Before/after RSS 4440812 / 4290000 & runtime 172.73 / 162.45. This is after having already done a full git gc before, data via
/usr/bin/time-v.
Jeff correctly pointed out elsewhere in this thread that RSS covers both heap (this is what I try to reduce) and some file cache (we mmap the whole pack file just to ease the reading) so RSS might not a good indicator of memory reduction. Any new freed memory should be used for cache which raises RSS back up. I think the RssAnon field in /proc/<pid>/status shows it better.
Quoted 8 lines
So not huge, but respectable.
We have a big repo, and this gets repacked on 6-8GB of memory on dev KVMs, so we're under a fair bit of memory pressure. git-gc slows things down a lot.
It would be really nice to have something that made it use drastically less memory at the cost of less efficient packs. Is the property that
Ahh.. less efficient. You may be more interested in [1] then. It avoids rewriting the base pack. Without the base pack, book keeping becomes much much cheaper.
We still read every single byte in all packs though (I think, unless you use pack-bitmap) and this amount of I/O affect the rest of the system too. Perhaps reducing core.packedgitwindowsize might make it friendlier to the OS, I don't know.
Quoted 11 lines
you need to spend give or take the size of .git/objects in memory something inherent, or just a limitation of the current implementation? I.e. could we do a first pass to pick some objects based on some heuristic, then repack them N at a time, and finally delete the now-obsolete packs?
Another thing I've dealt with is that on these machines their NFS-mounted storage gets exhausted (I'm told) due to some pathological operations git does during repack, I/O tends to get 5-6x slower. Of course ionice doesn't help because the local kernel doesn't know anything about how harmful it is.
[1] https://public-inbox.org/git/[email protected]/T/#u
Signature · 1 line
Duy
Re: [PATCH 00/11] Reduce pack-objects memory footprint
Quoted 7 lines · Duy Nguyen
Quoted 6 lines
We have a big repo, and this gets repacked on 6-8GB of memory on dev KVMs, so we're under a fair bit of memory pressure. git-gc slows things down a lot.
It would be really nice to have something that made it use drastically less memory at the cost of less efficient packs. Is the property that
Quoted 8 lines
Ahh.. less efficient. You may be more interested in [1] then. It avoids rewriting the base pack. Without the base pack, book keeping becomes much much cheaper.
We still read every single byte in all packs though (I think, unless you use pack-bitmap) and this amount of I/O affect the rest of the system too. Perhaps reducing core.packedgitwindowsize might make it friendlier to the OS, I don't know.
Yes, the ".keep" thing is actually quite expensive. We still do a complete rev-list to find all the objects we want, and then for each object say "is this in a pack with .keep?". And worse, the mru doesn't help there because even if we find it in the first pack, we have to keep looking to see if it's _another_ pack.
There are probably some low-hanging optimizations there (e.g., only looking in the .keep packs if that's all we're looking for; we may even do that already).
But I think fundamentally you'd do much better to generate the partial list of objects outside of pack-objects entirely, and then just feed it to pack-objects without using "--revs".
-Peff
v2 cover Reduce pack-objects memory footprint
In reply to Nguyễn Thái Ngọc DuyThe array of object_entry in pack-objects can take a lot of memory when pack-objects is run in "pack everything" mode. On linux-2.6.git, this array alone takes roughly 800MB.
This series reorders some fields and reduces field size... to keep this struct smaller. Its size goes from 136 bytes to 96 bytes (29%) on 64-bit linux and saves 260MB on linux-2.6.git.
v2 only differs in limits, documentation and a new escape hatch for the pack file limit.
Now the bad side:
- the number of pack files pack-objects can handle is reduced to 16k (previously unlimited, v1 has it at 4k)
- max delta chain is also limited to 4096 (previously practically unlimited), same as v1
- some patches are quite invasive (e.g. replacing pointer with uint32_t) and reduces readability a bit.
- it may be tricker to add more data in object_entry in the future.
In v1, if the total pack count is above the 4k limit, pack-objects dies. v2 is a bit smarter and only count packs that do not have the companion .keep files. This allows users with 16k+ pack files to continue to use pack-objects by first adding .keep to reduce pack count, repack, remove (some) .keep files, repack again...
While this process could be automated at least by 'git repack', given the unbelievably high limit 16k, I don't think it's worth doing it.
Interdiff
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +12 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
@@ -267,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +26 -14
@@ -1038,7 +1038,7 @@ static int want_object_in_pack(const struct object_id *oid,if (*found_pack) { want =want_found_object (exclude, *found_pack);if (want !=-1 ) -return want; + goto done; }list_for_each (pos, &packed_git_mru) {
@@ -1061,11 +1061,27 @@ static int want_object_in_pack(const struct object_id *oid,if (!exclude && want >0 )list_move (&p->mru, &packed_git_mru);if (want !=-1 ) -return want; + goto done; } } -return 1 ; + want =1 ; +done: +if (want && *found_pack && !(*found_pack)->index) { +struct packed_git *p = *found_pack; + +if (to_pack.in_pack_count >= (1 <<OE_IN_PACK_BITS )) +die (_ ("too many packs to handle in one go. " +"Please add .keep files to exclude\n" +"some pack files and keep the number " +"of non-kept files below %d." ), +1 <<OE_IN_PACK_BITS ); + + p->index = to_pack.in_pack_count++; + to_pack.in_pack[p->index] = p; + } + +return want; } static void create_object_entry(const struct object_id *oid,
@@ -1088,6 +1104,8 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { +if (found_pack->index <=0 ) +die ("BUG: found_pack should be NULL instead of having non-positive index" ); entry->in_pack_idx = found_pack->index; entry->in_pack_offset = found_offset; }
@@ -2978,18 +2996,12 @@ static int option_parse_unpack_unreachable(const struct option *opt,static void init_in_pack_mapping (struct packing_data *to_pack) { -struct packed_git *p; -int i =0 ; -/* let IN_PACK() return NULL if in_pack_idx is zero */ - to_pack->in_pack[i++] =NULL ; - -for (p = packed_git; p; p = p->next, i++) { -if (i >= (1 <<OE_IN_PACK_BITS )) -die ("BUG: too many packs to handle!" ); - to_pack->in_pack[i] = p; - p->index = i; - } + to_pack->in_pack[to_pack->in_pack_count++] =NULL ; +/* +* the rest is lazily initialized only for packs that we want +* in want_object_in_pack(). +*/ }int cmd_pack_objects (int argc,const char **argv,const char *prefix)
pack-objects.h +3 -2
@@ -3,7 +3,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 -#define OE_IN_PACK_BITS 12 +#define OE_IN_PACK_BITS 14#define IN_PACK_POS (to_pack, obj) \ (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects]
@@ -60,7 +60,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 96 */ +/* size: 96, bit_padding: 18 bits */ };struct packing_data {
@@ -71,6 +71,7 @@ struct packing_data {uint32_t index_size;unsigned int *in_pack_pos; +int in_pack_count;struct packed_git *in_pack[1 <<OE_IN_PACK_BITS ]; };
Nguyễn Thái Ngọc Duy (9):
pack-objects: document holes in struct object_entry.h
pack-objects: turn type and in_pack_type to bitfields
pack-objects: use bitfield for object_entry::dfs_state
pack-objects: use bitfield for object_entry::depth
pack-objects: note about in_pack_header_size
pack-objects: move in_pack_pos out of struct object_entry
pack-objects: move in_pack out of struct object_entry
pack-objects: refer to delta objects by index instead of pointer
pack-objects: reorder 'hash' to pack struct object_entry
Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 13 +- Documentation/git-repack.txt | 4 +- builtin/pack-objects.c | 207 +++++++++++++++++++---------- cache.h | 3 + object.h | 1 - pack-bitmap-write.c | 8 +- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +- pack-objects.h | 71 ++++++---- 10 files changed, 212 insertions(+), 102 deletions(-)
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 1/9 pack-objects: document holes in struct object_entry.h
--- pack-objects.h | 2 ++ 1 file changed, 2 insertions(+)
pack-objects.h +2 -0
@@ -28,6 +28,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ +/* XXX 28 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -40,6 +41,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; +/* size: 136, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 2/9 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyThis saves 8 bytes in sizeof(struct object_entry). On a large repository like linux-2.6.git (6.5M objects), this saves us 52MB memory.
--- builtin/pack-objects.c | 14 ++++++++++++-- cache.h | 2 ++ object.h | 1 - pack-objects.h | 8 ++++---- 4 files changed, 18 insertions(+), 7 deletions(-)
builtin/pack-objects.c +12 -2
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
@@ -1580,6 +1586,10 @@ static void drop_reused_delta(struct object_entry *entry)*/ entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); + }else { +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->type = type; } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-objects.h +4 -4
@@ -14,11 +14,11 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /** we do not pack this, but is available * to be used as the base object to delta
@@ -28,7 +28,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ -/* XXX 28 bits hole, try to pack */ +/* XXX 22 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -41,7 +41,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; -/* size: 136, padding: 4 */ +/* size: 128, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 3/9 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +20 -13
@@ -1,6 +1,21 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* + * State flags for depth-first search used for analyzing delta cycles. + * + * The depth is measured in delta-links to the base (so if A is a delta + * against B, then A has a depth of 1, and B a depth of 0). + */ +enum dfs_state { + DFS_NONE = 0, + DFS_ACTIVE, + DFS_DONE, + DFS_NUM_STATES +}; + struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
@@ -27,21 +42,13 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; + +/* XXX 20 bits hole, try to pack */ -/* XXX 22 bits hole, try to pack */ -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; -/* size: 128, padding: 4 */ + +/* size: 120 */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 4/9 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyThis does not give us any saving due to padding. But we will be able to save once we cut 4 bytes out of this struct in a subsequent patch.
Because of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 4 +++- Documentation/git-repack.txt | 4 +++- builtin/pack-objects.c | 4 ++++ pack-objects.h | 8 +++----- 5 files changed, 14 insertions(+), 7 deletions(-)
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +3 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth > (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +3 -5
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -43,12 +44,9 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; + unsigned depth:OE_DEPTH_BITS; -/* XXX 20 bits hole, try to pack */ - -int depth; - -/* size: 120 */ +/* size: 120, bit_padding: 8 bits */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 5/9 pack-objects: note about in_pack_header_size
In reply to Nguyễn Thái Ngọc DuyObject header in a pack is packed really tight (see pack-format.txt). Even with 8 bytes length, we need 9-10 bytes most, plus a hash (20 bytes). Which means this field only needs to store a number as big as 32 (5 bits).
This is trickier to pack tight though since a new hash algorithm is coming, the number of bits needed may quickly increase. So leave it for now.
--- pack-objects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
pack-objects.h +1 -1
@@ -32,7 +32,7 @@ struct object_entry {unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos; -unsigned char in_pack_header_size; +unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /*
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 6/9 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not). This saves us 8 bytes in struct object_entry.
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 8 ++++++-- 5 files changed, 17 insertions(+), 8 deletions(-)
builtin/pack-objects.c +2 -1
@@ -878,7 +878,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +IN_PACK_POS (to_pack, entry) = i;switch (entry->type) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return IN_PACK_POS (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =IN_PACK_POS (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +6 -2
@@ -4,6 +4,9 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 +#define IN_PACK_POS (to_pack, obj) \ + (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects] +/* * State flags for depth-first search used for analyzing delta cycles. *
@@ -31,7 +34,6 @@ struct object_entry {unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */
@@ -46,7 +48,7 @@ struct object_entry { unsigned dfs_state:OE_DFS_STATE_BITS; unsigned depth:OE_DEPTH_BITS; -/* size: 120, bit_padding: 8 bits */ +/* size: 112, bit_padding: 8 bits */ };struct packing_data {
@@ -55,6 +57,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 7/9 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
This technically saves 7 bytes. But we don't see any of that in practice due to padding. The saving becomes real when we pack this struct tighter later.
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
--- Documentation/git-pack-objects.txt | 9 +++++ builtin/pack-objects.c | 59 +++++++++++++++++++++++------- cache.h | 1 + pack-objects.h | 18 ++++++++- 4 files changed, 71 insertions(+), 16 deletions(-)
Documentation/git-pack-objects.txt +9 -0
@@ -269,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
builtin/pack-objects.c +45 -14
133 lines not on this page · raw message
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
pack-objects.h +16 -2
@@ -3,10 +3,14 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12 +#define OE_IN_PACK_BITS 14#define IN_PACK_POS (to_pack, obj) \ (to_pack)->in_pack_pos[(struct object_entry *)(obj) - (to_pack)->objects] +#define IN_PACK (to_pack, obj) \ + (to_pack)->in_pack[(obj)->in_pack_idx] +/* * State flags for depth-first search used for analyzing delta cycles. *
@@ -20,10 +24,14 @@ enum dfs_state {DFS_NUM_STATES }; +/* +* The size of struct nearly determines pack-objects's memory +* consumption. This struct is packed tight for that reason. When you +* add or reorder something in this struct, think a bit about this. +*/ struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */ -struct packed_git *in_pack;/* already in pack */ off_t in_pack_offset;struct object_entry *delta;/* delta base object */ struct object_entry *delta_child;/* deltified objects who bases me */
@@ -35,6 +43,7 @@ struct object_entry {unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ unsigned char in_pack_header_size;/* note: spare bits available! */ + unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /*
@@ -46,9 +55,12 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; + +/* XXX 8 bits hole, try to pack */ + unsigned depth:OE_DEPTH_BITS; -/* size: 112, bit_padding: 8 bits */ +/* size: 112, padding: 4, bit_padding: 18 bits */ };struct packing_data {
@@ -59,6 +71,8 @@ struct packing_data {uint32_t index_size;unsigned int *in_pack_pos; +int in_pack_count; +struct packed_git *in_pack[1 <<OE_IN_PACK_BITS ]; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 8/9 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyNotice that packing_data::nr_objects is uint32_t, we could only handle maximum 4G objects and can address all of them with an uint32_t. If we use a pointer here, we waste 4 bytes on 64 bit architecture.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
There are holes in this struct but this patch is already big. Struct packing can be done separately. Even with holes, we save 8 bytes per object_entry.
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 124 +++++++++++++++++++++++------------------ pack-objects.h | 14 +++-- 2 files changed, 78 insertions(+), 60 deletions(-)
builtin/pack-objects.c +70 -54
@@ -29,6 +29,20 @@#include "list.h" #include "packfile.h" +#define DELTA (obj) \ + ((obj)->delta_idx ? &to_pack.objects[(obj)->delta_idx - 1] : NULL) +#define DELTA_CHILD (obj) \ + ((obj)->delta_child_idx ? &to_pack.objects[(obj)->delta_child_idx - 1] : NULL) +#define DELTA_SIBLING (obj) \ + ((obj)->delta_sibling_idx ? &to_pack.objects[(obj)->delta_sibling_idx - 1] : NULL) + +#define CLEAR_DELTA (obj) (obj)->delta_idx = 0 +#define CLEAR_DELTA_CHILD (obj) (obj)->delta_child_idx = 0 +#define CLEAR_DELTA_SIBLING (obj) (obj)->delta_sibling_idx = 0 + +#define SET_DELTA (obj, val) (obj)->delta_idx = ((val) - to_pack.objects) + 1 +#define SET_DELTA_CHILD (obj, val) (obj)->delta_child_idx = ((val) - to_pack.objects) + 1 +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -125,11 +139,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -286,12 +300,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -315,7 +329,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -341,7 +355,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -377,8 +391,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -406,7 +420,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -425,7 +439,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -465,13 +479,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -486,7 +500,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (entry->type != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -538,12 +552,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +CLEAR_DELTA (e);break ;default :break ;
@@ -605,34 +619,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -643,7 +657,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -658,8 +672,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +CLEAR_DELTA_CHILD (&objects[i]); +CLEAR_DELTA_SIBLING (&objects[i]); }/*
@@ -669,11 +683,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1509,10 +1523,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ entry->type = entry->in_pack_type; - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1583,17 +1597,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +CLEAR_DELTA (entry); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1635,7 +1651,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1660,7 +1676,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1683,7 +1699,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1698,7 +1714,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1881,7 +1897,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1957,7 +1973,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1986,7 +2002,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1995,13 +2011,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2070,7 +2086,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2120,7 +2136,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2128,7 +2144,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2449,7 +2465,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +8 -6
@@ -33,11 +33,13 @@ struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */ off_t in_pack_offset; -struct object_entry *delta;/* delta base object */ -struct object_entry *delta_child;/* deltified objects who bases me */ -struct object_entry *delta_sibling;/* other deltified objects who -* uses the same base as me -*/ +uint32_t delta_idx;/* delta base object */ +uint32_t delta_child_idx;/* deltified objects who bases me */ +uint32_t delta_sibling_idx;/* other deltified objects who +* uses the same base as me +*/ +/* XXX 4 bytes hole, try to pack */ +void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */
@@ -60,7 +62,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 112, padding: 4, bit_padding: 18 bits */ +/* size: 104, padding: 4, bit_padding: 18 bits */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
PATCH v2 9/9 pack-objects: reorder 'hash' to pack struct object_entry
In reply to Nguyễn Thái Ngọc Duy--- pack-objects.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
pack-objects.h +2 -4
@@ -38,12 +38,10 @@ struct object_entry {uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -/* XXX 4 bytes hole, try to pack */ - +uint32_t hash;/* name hint hash */ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -uint32_t hash;/* name hint hash */ unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS;
@@ -62,7 +60,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 104, padding: 4, bit_padding: 18 bits */ +/* size: 96, bit_padding: 18 bits */ };struct packing_data {
Signature · 1 line
2.16.1.435.g8f24da2e1a
Re: [PATCH/RFC v2 0/9] Reduce pack-objects memory footprint
In reply to Nguyễn Thái Ngọc DuyQuoted 7 lines · Nguyễn Thái Ngọc Duy
The array of
object_entryin pack-objects can take a lot of memory when pack-objects is run in "pack everything" mode. On linux-2.6.git, this array alone takes roughly 800MB.This series reorders some fields and reduces field size... to keep this struct smaller. Its size goes from 136 bytes to 96 bytes (29%) on 64-bit linux and saves 260MB on linux-2.6.git.
And I continue to push this until someone screams "enough is enough!". This patch saves 4 more bytes. The trade off is, processing objects with offset beyond 4 GB will be slower. But I think this is a reasonable trade off.
The same trick could be done for "size" field in this struct (i.e. uncompressed object size greater than 32 bits must be read back from disk). Interestingly though, "size" is unsigned long which is 32 bits on Windows and nobody has complained about it so far, we could even just unconditionally reject objects larger than 4GB.
-- 8< --
Subject: [PATCH] pack-objects: shrink in_pack_offset for 4GB pack files
If a pack file is smaller than 4GB, pack offsets should fit within 32 bits. If not (which is not considered a common case), this field in_pack_location stores the object index instead (which still fits in 32 bits) and getting pack offset requires extra lookups through nth_packed_object_offset() function call.
This saves us 4 bytes but lose it to padding until this struct is shrunk further.
--- builtin/pack-objects.c | 26 ++++++++++++++----------- pack-objects.h | 44 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 13 deletions(-)
builtin/pack-objects.c +15 -11
@@ -43,6 +43,8 @@#define SET_DELTA (obj, val) (obj)->delta_idx = ((val) - to_pack.objects) + 1#define SET_DELTA_CHILD (obj, val) (obj)->delta_child_idx = ((val) - to_pack.objects) + 1 +#define IN_PACK_OFFSET (obj) oe_in_pack_offset(&to_pack, obj) +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -397,7 +399,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size); - offset = entry->in_pack_offset; + offset =IN_PACK_OFFSET (entry); revidx =find_pack_revindex (p, offset); datalen = revidx[1 ].offset - offset; if (!pack_to_stdout && p->index_version > 1 &&
@@ -1107,7 +1109,7 @@ static void create_object_entry(const struct object_id *oid,if (found_pack->index <=0 )die ("BUG: found_pack should be NULL instead of having non-positive index" ); entry->in_pack_idx = found_pack->index; - entry->in_pack_offset = found_offset; +oe_set_in_pack_offset (&to_pack, entry, found_offset); } entry->no_try_delta = no_try_delta;
@@ -1442,7 +1444,7 @@ static void check_object(struct object_entry *entry)unsigned char *buf, c;enum object_type type; - buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail); + buf =use_pack (p, &w_curs,IN_PACK_OFFSET (entry), &avail);/* * We want in_pack_type even if we do not reuse delta
@@ -1475,12 +1477,12 @@ static void check_object(struct object_entry *entry)case OBJ_REF_DELTA :if (reuse_delta && !entry->preferred_base) base_ref =use_pack (p, &w_curs, - entry->in_pack_offset + used,NULL ); +IN_PACK_OFFSET (entry) + used,NULL ); entry->in_pack_header_size = used +20 ;break ;case OBJ_OFS_DELTA : buf =use_pack (p, &w_curs, - entry->in_pack_offset + used,NULL ); +IN_PACK_OFFSET (entry) + used,NULL ); used_0 =0 ; c = buf[used_0++]; ofs = c &127 ;
@@ -1494,8 +1496,8 @@ static void check_object(struct object_entry *entry) c = buf[used_0++]; ofs = (ofs <<7 ) + (c &127 ); } - ofs = entry->in_pack_offset - ofs; -if (ofs <=0 || ofs >= entry->in_pack_offset) { + ofs =IN_PACK_OFFSET (entry) - ofs; +if (ofs <=0 || ofs >=IN_PACK_OFFSET (entry)) {error ("delta base offset out of bound for %s" ,oid_to_hex (&entry->idx.oid)); goto give_up;
@@ -1538,7 +1540,7 @@ static void check_object(struct object_entry *entry)* object size from the delta header. */ entry->size =get_size_from_delta (p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); +IN_PACK_OFFSET (entry) + entry->in_pack_header_size);if (entry->size ==0 ) goto give_up;unuse_pack (&w_curs);
@@ -1578,8 +1580,8 @@ static int pack_offset_sort(const void *_a, const void *_b)return -1 ;if (a_in_pack > b_in_pack)return 1 ; -return a->in_pack_offset < b->in_pack_offset ?-1 : - (a->in_pack_offset > b->in_pack_offset); +return IN_PACK_OFFSET (a) <IN_PACK_OFFSET (b) ?-1 : + (IN_PACK_OFFSET (a) >IN_PACK_OFFSET (b)); }/*
@@ -1614,7 +1616,9 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (IN_PACK (&to_pack, entry), entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (&to_pack, entry), +IN_PACK_OFFSET (entry), + &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
pack-objects.h +42 -2
78 lines not on this page · raw message
Signature · 3 lines
2.16.1.435.g8f24da2e1a -- 8< --
v3 cover Reduce pack-objects memory footprint
In reply to Nguyễn Thái Ngọc Duyv3 cleans up a bit to avoid the horrible macros that v2 adds, and adds some more documentation for this struct since it's getting harder to just look at the struct and see what field is related to what.
v3 also adds three more patches and reduces another 16 bytes (total struct reduction now is 41%). After this there's hardly anything else I could do. Two 64-bit fields left, but even if I shrink them, I'd lose it to padding. There's still one possibility to share in_pack_offset with idx.offset, but it's risky.
These three patches are made to optimize for the common case. The incommon cases will suffer some performance loss:
- 10/12 limits the cached compressed delta size to 64k (default 1000 bytes). If you normally have lots of huge deltas, you're going to take a hit because more deltas must be recreated at writing phase. Note that it does not stop pack-objects from creating deltas larger than 64k.
- 11/12 reduces uncompressed object size to 4GB. Whenever we need to read object size of those larger than that, we read the pack again to retrieve the information, which is much slower than accessing a piece of memory. Again I'm assuming these giant blobs are really really rare that this performance hit won't matter.
- 12/12 is similar to 11/12 and reduces uncompressed delta size to 4GB. Frankly a 4GB delta is still ridiculous, but I don't think we gain more by shrinking it further. If your packs have one of those giant deltas, it still works, delta_size will be read back from the pack again.
The following interdiff does _NOT_ cover the new patches, just the first nine that v2 has.
builtin/pack-objects.c +26 -53
@@ -29,19 +29,13 @@#include "list.h" #include "packfile.h" -#define DELTA (obj) \ - ((obj)->delta_idx ? &to_pack.objects[(obj)->delta_idx - 1] : NULL) -#define DELTA_CHILD (obj) \ - ((obj)->delta_child_idx ? &to_pack.objects[(obj)->delta_child_idx - 1] : NULL) -#define DELTA_SIBLING (obj) \ - ((obj)->delta_sibling_idx ? &to_pack.objects[(obj)->delta_sibling_idx - 1] : NULL) - -#define CLEAR_DELTA (obj) (obj)->delta_idx = 0 -#define CLEAR_DELTA_CHILD (obj) (obj)->delta_child_idx = 0 -#define CLEAR_DELTA_SIBLING (obj) (obj)->delta_sibling_idx = 0 - -#define SET_DELTA (obj, val) (obj)->delta_idx = ((val) - to_pack.objects) + 1 -#define SET_DELTA_CHILD (obj, val) (obj)->delta_child_idx = ((val) - to_pack.objects) + 1 +#define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA (obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),
@@ -381,7 +375,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p =IN_PACK (&to_pack, entry); +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -492,7 +486,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!IN_PACK (&to_pack, entry)) +else if (!IN_PACK (entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (entry->type ==OBJ_REF_DELTA || entry->type ==OBJ_OFS_DELTA )/* check_object() decided it for us ... */
@@ -557,7 +551,7 @@ static enum write_one_status write_one(struct hashfile *f,switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ -CLEAR_DELTA (e); +SET_DELTA (e,NULL );break ;default :break ;
@@ -672,8 +666,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; -CLEAR_DELTA_CHILD (&objects[i]); -CLEAR_DELTA_SIBLING (&objects[i]); +SET_DELTA_CHILD (&objects[i],NULL ); +SET_DELTA_SIBLING (&objects[i],NULL ); }/*
@@ -1067,19 +1061,8 @@ static int want_object_in_pack(const struct object_id *oid, want =1 ; done: -if (want && *found_pack && !(*found_pack)->index) { -struct packed_git *p = *found_pack; - -if (to_pack.in_pack_count >= (1 <<OE_IN_PACK_BITS )) -die (_ ("too many packs to handle in one go. " -"Please add .keep files to exclude\n" -"some pack files and keep the number " -"of non-kept files below %d." ), -1 <<OE_IN_PACK_BITS ); - - p->index = to_pack.in_pack_count++; - to_pack.in_pack[p->index] = p; - } +if (want && *found_pack && !(*found_pack)->index) +oe_add_pack (&to_pack, *found_pack);return want; }
@@ -1104,9 +1087,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { -if (found_pack->index <=0 ) -die ("BUG: found_pack should be NULL instead of having non-positive index" ); - entry->in_pack_idx = found_pack->index; +oe_set_in_pack (entry, found_pack); entry->in_pack_offset = found_offset; }
@@ -1431,8 +1412,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (IN_PACK (&to_pack, entry)) { -struct packed_git *p =IN_PACK (&to_pack, entry); +if (IN_PACK (entry)) { +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1567,8 +1548,8 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; -const struct packed_git *a_in_pack =IN_PACK (&to_pack, a); -const struct packed_git *b_in_pack =IN_PACK (&to_pack, b); +const struct packed_git *a_in_pack =IN_PACK (a); +const struct packed_git *b_in_pack =IN_PACK (b);/* avoid filesystem trashing with loose objects */ if (!a_in_pack && !b_in_pack)
@@ -1609,12 +1590,12 @@ static void drop_reused_delta(struct object_entry *entry)else idx = &oe->delta_sibling_idx; } -CLEAR_DELTA (entry); +SET_DELTA (entry,NULL ); entry->depth =0 ; oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (IN_PACK (&to_pack, entry), entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1884,8 +1865,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta &&IN_PACK (&to_pack, trg_entry) && -IN_PACK (&to_pack, trg_entry) ==IN_PACK (&to_pack, src_entry) && +if (reuse_delta &&IN_PACK (trg_entry) && +IN_PACK (trg_entry) ==IN_PACK (src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -2994,16 +2975,6 @@ static int option_parse_unpack_unreachable(const struct option *opt,return 0 ; } -static void init_in_pack_mapping (struct packing_data *to_pack) -{ -/* let IN_PACK() return NULL if in_pack_idx is zero */ - to_pack->in_pack[to_pack->in_pack_count++] =NULL ; -/* -* the rest is lazily initialized only for packs that we want -* in want_object_in_pack(). -*/ -} -int cmd_pack_objects (int argc,const char **argv,const char *prefix) {int use_internal_rev_list =0 ;
@@ -3236,7 +3207,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } } -init_in_pack_mapping (&to_pack); + +/* make sure IN_PACK(0) return NULL */ +oe_add_pack (&to_pack,NULL );if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );
pack-bitmap-write.c +2 -2
@@ -64,7 +64,7 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; -IN_PACK_POS (to_pack, entry) = i; +oe_set_in_pack_pos (to_pack, entry, i);switch (entry->type) {case OBJ_COMMIT :
@@ -149,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return IN_PACK_POS (writer.to_pack, entry); +return oe_in_pack_pos (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] =IN_PACK_POS (mapping, oe) +1 ; + reposition[i] =oe_in_pack_pos (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-objects.h +153 -9
178 lines not on this page · raw message
Nguyễn Thái Ngọc Duy (12):
pack-objects: a bit of document about struct object_entry
pack-objects: turn type and in_pack_type to bitfields
pack-objects: use bitfield for object_entry::dfs_state
pack-objects: use bitfield for object_entry::depth
pack-objects: note about in_pack_header_size
pack-objects: move in_pack_pos out of struct object_entry
pack-objects: move in_pack out of struct object_entry
pack-objects: refer to delta objects by index instead of pointer
pack-objects: reorder 'hash' to pack struct object_entry
pack-objects: shrink z_delta_size field in struct object_entry
pack-objects: shrink size field in struct object_entry
pack-objects: shrink delta_size field in struct object_entry
Documentation/config.txt | 4 +- Documentation/git-pack-objects.txt | 13 +- Documentation/git-repack.txt | 4 +- builtin/pack-objects.c | 269 +++++++++++++++++---------- cache.h | 3 + object.h | 1 - pack-bitmap-write.c | 8 +- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +- pack-objects.h | 288 ++++++++++++++++++++++++++--- 10 files changed, 460 insertions(+), 136 deletions(-)
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 1/12 pack-objects: a bit of document about struct object_entry
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what. This also documents the holes in this struct according to pahole.
A couple of notes on shrinking the struct:
1) The reader may notice one thing from this document and the shrinking business. If "delta" is NULL, all other delta-related fields should be irrelevant. We could group all these in a separate struct and replace them all with a pointer to this struct (allocated separately).
This does not help much though since 85% of objects are deltified (source: linux-2.6.git). The gain is only from non-delta objects, which is not that significant.
2) The field in_pack_offset and idx.offset could be merged. But we need to be very careful. Up until the very last phase (object writing), idx.offset is not used and can hold in_pack_offset. Then idx.offset will be updated with _destination pack's_ offset, not source's. But since we always write delta's bases first, and we only use in_pack_offset in writing phase when we reuse objects, we should be ok?
--- pack-objects.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+)
pack-objects.h +48 -0
@@ -1,6 +1,52 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* + * basic object info + * ----------------- + * idx.oid is filled up before delta searching starts. idx.crc32 and + * is only valid after the object is written down and will be used for + * generating the index. idx.offset will be both gradually set and + * used in writing phase (base objects get offset first, then deltas + * refer to them) + * + * "size" is the uncompressed object size. Compressed size is not + * cached (ie. raw data in a pack) but available via revindex. + * + * "hash" contains a path name hash which is used for sorting the + * delta list and also during delta searching. Once prepare_pack() + * returns it's no longer needed. + * + * source pack info + * ---------------- + * The (in_pack, in_pack_offset, in_pack_header_size) tuple contains + * the location of the object in the source pack, with or without + * header. + * + * "type" and "in_pack_type" both describe object type. in_pack_type + * may contain a delta type, while type is always the canonical type. + * + * deltas + * ------ + * Delta links (delta, delta_child and delta_sibling) are created + * reflect that delta graph from the source pack then updated or added + * during delta searching phase when we find better deltas. + * + * delta_child and delta_sibling are last needed in + * compute_write_order(). "delta" and "delta_size" must remain valid + * at object writing phase in case the delta is not cached. + * + * If a delta is cached in memory and is compressed, "delta" points to + * the data and z_delta_size contains the compressed size. If it's + * uncompressed [1], z_delta_size must be zero. delta_size is always + * the uncompressed size and must be valid even if the delta is not + * cached. Delta recreation technically only depends on "delta" + * pointer, but delta_size is still used to verify it's the same as + * before. + * + * [1] during try_delta phase we don't bother with compressing because + * the delta could be quickly replaced with a better one. + */ struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
@@ -28,6 +74,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ +/* XXX 28 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -40,6 +87,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; +/* size: 136, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
Re: [PATCH/RFC v3 01/12] pack-objects: a bit of document about struct object_entry
Quoted 7 lines · Nguyễn Thái Ngọc Duy
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what. This also documents the holes in this struct according to pahole.
A couple of notes on shrinking the struct:
Quoted 8 lines
1) The reader may notice one thing from this document and the shrinking business. If "delta" is NULL, all other delta-related fields should be irrelevant. We could group all these in a separate struct and replace them all with a pointer to this struct (allocated separately).
This does not help much though since 85% of objects are deltified (source: linux-2.6.git). The gain is only from non-delta objects, which is not that significant.
OK.
Quoted 6 lines
2) The field
in_pack_offsetand idx.offset could be merged. But we need to be very careful. Up until the very last phase (object writing), idx.offset is not used and can holdin_pack_offset. Then idx.offset will be updated with_destinationpack's_ offset, not source's. But since we always write delta's bases first, and we only usein_pack_offsetin writing phase when we reuse objects, we should be ok?
By separating the processing in strict phases, I do think the result would be OK, but at the same time, that does smell like an invitation for future bugs.
Quoted 8 lines
+/*
+ * basic object info
+ * -----------------
+ * idx.oid is filled up before delta searching starts. idx.crc32 and
+ * is only valid after the object is written down and will be used for
+ * generating the index. idx.offset will be both gradually set and
+ * used in writing phase (base objects get offset first, then deltas
+ * refer to them)
Here, I'd feel that "written out" somehow would sound more natural than "written down", but that is perhaps because I've seen it used elsewhere and I am confusing familiarlity with naturalness. In any case, if we mean "written to the resulting packdata stream", saying that to be more explicit is probably a good idea. We compute crc32 and learn the offset for each object as we write them to the result.
Quoted 2 lines
+ * If a delta is cached in memory and is compressed, "delta" points to
+ * the data andz_delta_sizecontains the compressed size. If it's
Isn't it "delta_data" (aot "delta") that points at the cached delta data?
Quoted 28 lines
+ * uncompressed [1],
z_delta_sizemust be zero.delta_sizeis always
+ * the uncompressed size and must be valid even if the delta is not
+ * cached. Delta recreation technically only depends on "delta"
+ * pointer, butdelta_sizeis still used to verify it's the same as
+ * before.
+ *
+ * [1] duringtry_deltaphase we don't bother with compressing because
+ * the delta could be quickly replaced with a better one.
+ */
structobject_entry{pack-objects.h +2
-0struct pack_idx_entry idx;unsigned long size;/* uncompressed size */ @@ -28,6 +74,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ +/* XXX 28 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. * @@ -40,6 +87,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; +/* size: 136, padding: 4 */ }; struct packing_data {
PATCH v3 2/12 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyThis saves 8 bytes in sizeof(struct object_entry). On a large repository like linux-2.6.git (6.5M objects), this saves us 52MB memory.
--- builtin/pack-objects.c | 14 ++++++++++++-- cache.h | 2 ++ object.h | 1 - pack-objects.h | 8 ++++---- 4 files changed, 18 insertions(+), 7 deletions(-)
builtin/pack-objects.c +12 -2
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
@@ -1580,6 +1586,10 @@ static void drop_reused_delta(struct object_entry *entry)*/ entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); + }else { +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->type = type; } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-objects.h +4 -4
@@ -60,11 +60,11 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size; + unsigned type:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /** we do not pack this, but is available * to be used as the base object to delta
@@ -74,7 +74,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ -/* XXX 28 bits hole, try to pack */ +/* XXX 22 bits hole, try to pack */ /* * State flags for depth-first search used for analyzing delta cycles. *
@@ -87,7 +87,7 @@ struct object_entry {DFS_DONE } dfs_state;int depth; -/* size: 136, padding: 4 */ +/* size: 128, padding: 4 */ }; struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
Re: [PATCH/RFC v3 02/12] pack-objects: turn type and in_pack_type to bitfields
builtin/pack-objects.c static void drop_reused_delta(struct object_entry *entry) · line 1588
Quoted 13 lines · Nguyễn Thái Ngọc Duy
builtin/pack-objects.c +1
-1@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; @@ -1580,6 +1586,10 @@ static void drop_reused_delta(struct object_entry *entry)*/ entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size);
The comment immediately before this pre-context reads as such:
/*
* We failed to get the info from this pack for some reason;
* fall back to sha1_object_info, which may find another copy.
* And if that fails, the error will be recorded in entry->type
* and dealt with in prepare_pack().
*/
The rest of the code relies on the ability of entry->type to record the error by storing an invalid (negative) type; otherwise, it cannot detect an error where (1) the entry in _this_ pack was corrupt, and
(2) we wished to find another copy of the object elsewhere (which would overwrite the negative entry->type we assign here), but we didn't find any.
How should we propagate the error we found here down the control flow in this new code?
builtin/pack-objects.c static void drop_reused_delta(struct object_entry *entry) · line 1592
Quoted 4 lines
builtin/pack-objects.c +4
-0+ }else { +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->type = type;
The BUG() on this side is sensible, as packed_object_info() shouldn't report success when it stored negative result in *oi.typep anyway.
Quoted 3 lines
pack-objects.h +2
-0unsigned char in_pack_header_size; + unsigned type:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */
Re: [PATCH/RFC v3 02/12] pack-objects: turn type and in_pack_type to bitfields
Quoted 32 lines · Junio C Hamano
Review static void drop_reused_delta(struct object_entry *entry) · line 1588
Quoted 13 lines · Nguyễn Thái Ngọc Duy
(quoted diff) +1
-1@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth = 0; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type; if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) { /* * We failed to get the info from this pack for some reason;@@ -1580,6 +1586,10 @@ static void drop_reused_delta(struct object_entry *entry) */ entry->type = sha1_object_info(entry->idx.oid.hash, &entry->size);The comment immediately before this pre-context reads as such:
/*
* We failed to get the info from this pack for some reason;
* fall back tosha1_object_info, which may find another copy.
* And if that fails, the error will be recorded inentry->type
* and dealt with inprepare_pack().
*/The rest of the code relies on the ability of
entry->typeto record the error by storing an invalid (negative) type; otherwise, it cannot detect an error where (1) the entry in _this_ pack was corrupt, and
(2) we wished to find another copy of the object elsewhere (which would overwrite the negativeentry->typewe assign here), but we didn't find any.
Quoted 2 lines
How should we propagate the error we found here down the control flow in this new code?
Good catch! I don't have any magic trick to do this, so I'm adding an extra bit to store type validity. Something like this as a fixup patch (I'll resend the whole series soon).
builtin/pack-objects.c +24 -23
@@ -265,7 +265,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstruct git_istream *st =NULL ;if (!usable_delta) { -if (entry->type ==OBJ_BLOB && +if (oe_type (entry) ==OBJ_BLOB && entry->size > big_file_threshold && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;
@@ -371,7 +371,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset; -enum object_type type = entry->type; +enum object_type type =oe_type (entry);off_t datalen;unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];
@@ -480,11 +480,12 @@ static off_t write_object(struct hashfile *f, to_reuse =0 ;/* explicit */ else if (!entry->in_pack) to_reuse =0 ;/* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA)/* check_object() decided it for us ... */ to_reuse = usable_delta;/* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ else if (entry->delta) to_reuse =0 ;/* we want to pack afresh */
@@ -705,8 +706,8 @@ static struct object_entry **compute_write_order(void)* And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_COMMIT && - objects[i].type !=OBJ_TAG ) +if (oe_type (&objects[i]) !=OBJ_COMMIT && +oe_type (&objects[i]) !=OBJ_TAG )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -715,7 +716,7 @@ static struct object_entry **compute_write_order(void)* And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_TREE ) +if (oe_type (&objects[i]) !=OBJ_TREE )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -1067,7 +1068,7 @@ static void create_object_entry(const struct object_id *oid, entry =packlist_alloc (&to_pack, oid->hash, index_pos); entry->hash = hash;if (type) - entry->type = type; +oe_set_type (entry, type);if (exclude) entry->preferred_base =1 ;else
@@ -1433,9 +1434,9 @@ static void check_object(struct object_entry *entry)switch (entry->in_pack_type) {default :/* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->in_pack_header_size = used; -if (entry->type <OBJ_COMMIT || entry->type >OBJ_BLOB ) +if (oe_type (entry) <OBJ_COMMIT ||oe_type (entry) >OBJ_BLOB ) goto give_up;unuse_pack (&w_curs);return ;
@@ -1489,7 +1490,7 @@ static void check_object(struct object_entry *entry)* deltify other objects against, in order to avoid * circular deltas. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->delta = base_entry; entry->delta_size = entry->size; entry->delta_sibling = base_entry->delta_child;
@@ -1498,7 +1499,7 @@ static void check_object(struct object_entry *entry)return ; } -if (entry->type) { +if (oe_type (entry)) {/* * This must be a delta and we already know what the * final object type is. Let's extract the actual
@@ -1521,7 +1522,7 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored
@@ -1584,12 +1585,10 @@ static void drop_reused_delta(struct object_entry *entry)* And if that fails, the error will be recorded in entry->type * and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &entry->size)); }else { -if (type <0 ) -die ("BUG: invalid type %d" , type); - entry->type = type; +oe_set_type (entry, type); } }
@@ -1757,10 +1756,12 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +enum object_type a_type =oe_type (a); +enum object_type b_type =oe_type (b); -if (a->type > b->type) +if (a_type > b_type)return -1 ; -if (a->type < b->type) +if (a_type < b_type)return 1 ;if (a->hash > b->hash)return -1 ;
@@ -1836,7 +1837,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,void *delta_buf;/* Don't bother doing diffs between different types */ -if (trg_entry->type != src_entry->type) +if (oe_type (trg_entry) !=oe_type (src_entry))return -1 ;/*
@@ -2442,11 +2443,11 @@ static void prepare_pack(int window, int depth)if (!entry->preferred_base) { nr_deltas++; -if (entry->type <0 ) +if (oe_type (entry) <0 )die ("unable to get type of object %s" ,oid_to_hex (&entry->idx.oid)); }else { -if (entry->type <0 ) { +if (oe_type (entry) <0 ) {/* * This object is not found, but we * don't have to include it anyway.
pack-objects.h +17 -1
@@ -58,8 +58,9 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ - unsigned type:TYPE_BITS; + unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ + unsigned type_valid:1;uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size;
@@ -122,4 +123,19 @@ static inline uint32_t pack_name_hash(const char *name) return hash; } +static inline enum object_type oe_type (const struct object_entry *e) +{ +return e->type_valid ? e->type_ :OBJ_BAD ; +} + +static inline void oe_set_type (struct object_entry *e, +enum object_type type) +{ +if (type >=OBJ_ANY ) +die ("BUG: OBJ_ANY cannot be set in pack-objects code" ); + + e->type_valid = type >=0 ; + e->type_ = (unsigned )type; +} +#endif
PATCH v3 3/12 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 33 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 13 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +20 -13
@@ -1,6 +1,21 @@#ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* +* State flags for depth-first search used for analyzing delta cycles. +* +* The depth is measured in delta-links to the base (so if A is a delta +* against B, then A has a depth of 1, and B a depth of 0). +*/ +enum dfs_state { +DFS_NONE =0 , +DFS_ACTIVE , +DFS_DONE , +DFS_NUM_STATES +}; +/* * basic object info * -----------------
@@ -73,21 +88,13 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; + +/* XXX 20 bits hole, try to pack */ -/* XXX 22 bits hole, try to pack */ -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; -/* size: 128, padding: 4 */ + +/* size: 120 */ };struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 4/12 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyThis does not give us any saving due to padding. But we will be able to save once we cut 4 bytes out of this struct in a subsequent patch.
Because of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 4 +++- Documentation/git-repack.txt | 4 +++- builtin/pack-objects.c | 4 ++++ pack-objects.h | 8 +++----- 5 files changed, 14 insertions(+), 7 deletions(-)
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +3 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth > (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +3 -5
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -89,12 +90,9 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; + unsigned depth:OE_DEPTH_BITS; -/* XXX 20 bits hole, try to pack */ - -int depth; - -/* size: 120 */ +/* size: 120, bit_padding: 8 bits */ };struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
Re: [PATCH/RFC v3 04/12] pack-objects: use bitfield for object_entry::depth
builtin/pack-objects.c int cmd_pack_objects(int argc, const char **argv, const char *prefix) · line 3074
Quoted 6 lines · Nguyễn Thái Ngọc Duy
builtin/pack-objects.c +5
-0+ Maximum value is 4095. +if (depth > (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +
Do I see an off-by-one here? Ie.
if ((1 << OE_DEPATH_BITS) <= depth)
die("depth that is 4096 or deeper is too much");PATCH v3 5/12 pack-objects: note about in_pack_header_size
In reply to Nguyễn Thái Ngọc DuyObject header in a pack is packed really tight (see pack-format.txt). Even with 8 bytes length, we need 9-10 bytes most, plus a hash (20 bytes). Which means this field only needs to store a number as big as 32 (5 bits).
This is trickier to pack tight though since a new hash algorithm is coming, the number of bits needed may quickly increase. So leave it for now.
--- pack-objects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
pack-objects.h +1 -1
@@ -78,7 +78,7 @@ struct object_entry {unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ unsigned int in_pack_pos; -unsigned char in_pack_header_size; +unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned preferred_base:1; /*
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 6/12 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not). This saves us 8 bytes in struct object_entry.
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 18 ++++++++++++++++-- 5 files changed, 27 insertions(+), 8 deletions(-)
builtin/pack-objects.c +2 -1
@@ -878,7 +878,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +oe_set_in_pack_pos (to_pack, entry, i);switch (entry->type) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return oe_in_pack_pos (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =oe_in_pack_pos (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +16 -2
@@ -77,7 +77,6 @@ struct object_entry {unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned type:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */
@@ -92,7 +91,7 @@ struct object_entry { unsigned dfs_state:OE_DFS_STATE_BITS; unsigned depth:OE_DEPTH_BITS; -/* size: 120, bit_padding: 8 bits */ +/* size: 112, bit_padding: 8 bits */ };struct packing_data {
@@ -101,6 +100,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
@@ -131,4 +132,17 @@ static inline uint32_t pack_name_hash(const char *name) return hash; } +static inline unsigned int oe_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e) +{ +return pack->in_pack_pos[e - pack->objects]; +} + +static inline void oe_set_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e, +unsigned int pos) +{ + pack->in_pack_pos[e - pack->objects] = pos; +} +#endif
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 7/12 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
This technically saves 7 bytes. But we don't see any of that in practice due to padding. The saving becomes real when we pack this struct tighter later.
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
--- Documentation/git-pack-objects.txt | 9 ++++++ builtin/pack-objects.c | 40 +++++++++++++++--------- cache.h | 1 + pack-objects.h | 49 ++++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 16 deletions(-)
Documentation/git-pack-objects.txt +9 -0
@@ -269,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
builtin/pack-objects.c +26 -14
@@ -29,6 +29,8 @@#include "list.h" #include "packfile.h" +#define IN_PACK (obj) oe_in_pack(&to_pack, obj) +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -367,7 +369,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p = entry->in_pack; +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -478,7 +480,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!entry->in_pack) +else if (!IN_PACK (entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (entry->type ==OBJ_REF_DELTA || entry->type ==OBJ_OFS_DELTA )/* check_object() decided it for us ... */
@@ -1024,7 +1026,7 @@ static int want_object_in_pack(const struct object_id *oid,if (*found_pack) { want =want_found_object (exclude, *found_pack);if (want !=-1 ) -return want; + goto done; }list_for_each (pos, &packed_git_mru) {
@@ -1047,11 +1049,16 @@ static int want_object_in_pack(const struct object_id *oid,if (!exclude && want >0 )list_move (&p->mru, &packed_git_mru);if (want !=-1 ) -return want; + goto done; } } -return 1 ; + want =1 ; +done: +if (want && *found_pack && !(*found_pack)->index) +oe_add_pack (&to_pack, *found_pack); + +return want; } static void create_object_entry(const struct object_id *oid,
@@ -1074,7 +1081,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { - entry->in_pack = found_pack; +oe_set_in_pack (entry, found_pack); entry->in_pack_offset = found_offset; }
@@ -1399,8 +1406,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (entry->in_pack) { -struct packed_git *p = entry->in_pack; +if (IN_PACK (entry)) { +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1535,14 +1542,16 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +const struct packed_git *a_in_pack =IN_PACK (a); +const struct packed_git *b_in_pack =IN_PACK (b);/* avoid filesystem trashing with loose objects */ -if (!a->in_pack && !b->in_pack) +if (!a_in_pack && !b_in_pack)return oidcmp (&a->idx.oid, &b->idx.oid); -if (a->in_pack < b->in_pack) +if (a_in_pack < b_in_pack)return -1 ; -if (a->in_pack > b->in_pack) +if (a_in_pack > b_in_pack)return 1 ;return a->in_pack_offset < b->in_pack_offset ?-1 : (a->in_pack_offset > b->in_pack_offset);
@@ -1578,7 +1587,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1848,8 +1857,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && +if (reuse_delta &&IN_PACK (trg_entry) && +IN_PACK (trg_entry) ==IN_PACK (src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -3191,6 +3200,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } +/* make sure IN_PACK(0) return NULL */ +oe_add_pack (&to_pack,NULL ); +if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );if (!use_internal_rev_list)
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
pack-objects.h +47 -2
91 lines not on this page · raw message
Signature · 1 line
2.16.2.873.g32ff258c87
Re: [PATCH/RFC v3 07/12] pack-objects: move in_pack out of struct object_entry
Quoted 10 lines · Nguyễn Thái Ngọc Duy
Instead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index isntead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
This technically saves 7 bytes. But we don't see any of that in practice due to padding. The saving becomes real when we pack this struct tighter later.
Somehow 7 and 16k do not add up.
We use 8 bytes in the original code, and a solution that potentially saves 7 bytes would use only 1 byte instead of the original 8, which would allow us to index/identify 1<<8 == 256 packs, but for some reason we can handle up to 16k.
Quoted 3 lines
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
;-)
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack, · line 166
Quoted 9 lines
pack-objects.h +9
-0+static inline unsigned int oe_add_pack (struct packing_data *pack, +struct packed_git *p) +{ +if (pack->in_pack_count >= (1 <<OE_IN_PACK_BITS )) +die (_ ("too many packs to handle in one go. " +"Please add .keep files to exclude\n" +"some pack files and keep the number " +"of non-kept files below %d." ), +1 <<OE_IN_PACK_BITS );
OK.
PATCH v3 8/12 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyNotice that packing_data::nr_objects is uint32_t, we could only handle maximum 4G objects and can address all of them with an uint32_t. If we use a pointer here, we waste 4 bytes on 64 bit architecture.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
There are holes in this struct but this patch is already big. Struct packing can be done separately. Even with holes, we save 8 bytes per object_entry.
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 116 ++++++++++++++++++++++------------------- pack-objects.h | 71 ++++++++++++++++++++++--- 2 files changed, 127 insertions(+), 60 deletions(-)
builtin/pack-objects.c +62 -54
@@ -30,6 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA (obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),
@@ -127,11 +133,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -288,12 +294,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -317,7 +323,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -343,7 +349,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -379,8 +385,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -408,7 +414,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -427,7 +433,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -467,13 +473,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -488,7 +494,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (entry->type != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -540,12 +546,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +SET_DELTA (e,NULL );break ;default :break ;
@@ -607,34 +613,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -645,7 +651,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -660,8 +666,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +SET_DELTA_CHILD (&objects[i],NULL ); +SET_DELTA_SIBLING (&objects[i],NULL ); }/*
@@ -671,11 +677,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1498,10 +1504,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ entry->type = entry->in_pack_type; - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1572,17 +1578,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +SET_DELTA (entry,NULL ); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1624,7 +1632,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1649,7 +1657,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1672,7 +1680,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1687,7 +1695,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1870,7 +1878,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1946,7 +1954,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1975,7 +1983,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1984,13 +1992,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2059,7 +2067,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2109,7 +2117,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2117,7 +2125,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2438,7 +2446,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +65 -6
87 lines not on this page · raw message
Signature · 1 line
2.16.2.873.g32ff258c87
Re: [PATCH/RFC v3 08/12] pack-objects: refer to delta objects by index instead of pointer
Quoted 3 lines · Nguyễn Thái Ngọc Duy
Notice that
packing_data::nr_objectsisuint32_t, we could only handle maximum 4G objects and can address all of them with anuint32_t. If we use a pointer here, we waste 4 bytes on 64 bit architecture.
Some things are left unsaid or left unclear and make readers stutter a bit while reading this paragraph. We can address them with uint32_t only because we happen to have a linear array of all objects involved already, i.e. the pack->objects[] array. The readers are forced to rephrase the above in their mind
... and each of them can be identified with an uint32_t. Because we have all of these objects in pack->objects[], we can replace the "delta" field in each object entry that points at its delta base object with uint32_t index into this array to save memory (on 64-bit arch, 8-byte pointer gets shrunk to 4-byte uint).
or something like that before understanding why this is a valid memory footprint optimization.
PATCH v3 9/12 pack-objects: reorder 'hash' to pack struct object_entry
In reply to Nguyễn Thái Ngọc Duy--- pack-objects.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
pack-objects.h +2 -4
@@ -77,12 +77,10 @@ struct object_entry {uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -/* XXX 4 bytes hole, try to pack */ - +uint32_t hash;/* name hint hash */ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -uint32_t hash;/* name hint hash */ unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS;
@@ -101,7 +99,7 @@ struct object_entry { unsigned depth:OE_DEPTH_BITS; -/* size: 104, padding: 4, bit_padding: 18 bits */ +/* size: 96, bit_padding: 18 bits */ };struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 10/12 pack-objects: shrink z_delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyWe only cache deltas when it's smaller than a certain limit. This limit defaults to 1000 but save its compressed length in a 64-bit field. Shrink that field down to 16 bits, so you can only cache 65kb deltas. Larger deltas must be recomputed at when the pack is written down.
This saves us 8 bytes (some from previous bit padding).
--- Documentation/config.txt | 3 ++- builtin/pack-objects.c | 22 ++++++++++++++++------ pack-objects.h | 11 ++++++++--- 3 files changed, 26 insertions(+), 10 deletions(-)
Documentation/config.txt +2 -1
@@ -2449,7 +2449,8 @@ pack.deltaCacheLimit:: The maximum size of a delta, that is cached in linkgit:git-pack-objects[1]. This cache is used to speed up the writing object phase by not having to recompute the final delta - result once the best match for all objects is found. Defaults to 1000. + result once the best match for all objects is found. + Defaults to 1000. Maximum value is 65535. pack.threads:: Specifies the number of threads to spawn when searching for best
builtin/pack-objects.c +16 -6
@@ -2105,12 +2105,19 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* between writes at that moment. */ if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size =do_compress (&entry->delta_data, - entry->delta_size); -cache_lock (); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; -cache_unlock (); +unsigned long size; + + size =do_compress (&entry->delta_data, entry->delta_size); + entry->z_delta_size = size; +if (entry->z_delta_size == size) { +cache_lock (); + delta_cache_size -= entry->delta_size; + delta_cache_size += entry->z_delta_size; +cache_unlock (); + }else { +FREE_AND_NULL (entry->delta_data); + entry->z_delta_size =0 ; + } }/* if we made n a delta, and if n is already at max
@@ -3089,6 +3096,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (depth > (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), depth, (1 <<OE_DEPTH_BITS )); +if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS )) +die (_ ("pack.deltaCacheLimit is greater than maximum limit %d" ), +1 <<OE_Z_DELTA_BITS );argv_array_push (&rp,"pack-objects" );if (thin) {
pack-objects.h +8 -3
@@ -4,6 +4,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14 +#define OE_Z_DELTA_BITS 16/* * State flags for depth-first search used for analyzing delta cycles.
@@ -80,7 +81,6 @@ struct object_entry {uint32_t hash;/* name hint hash */ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ -unsigned long z_delta_size;/* delta data size (compressed) */ unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS;
@@ -93,13 +93,18 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ - unsigned dfs_state:OE_DFS_STATE_BITS;/* XXX 8 bits hole, try to pack */ + unsigned dfs_state:OE_DFS_STATE_BITS; unsigned depth:OE_DEPTH_BITS; +/* +* if delta_data contains a compressed delta, this contains +* the compressed length +*/ + unsigned z_delta_size:OE_Z_DELTA_BITS; -/* size: 96, bit_padding: 18 bits */ +/* size: 88, bit_padding: 2 bits */ };struct packing_data {
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 11/12 pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyIt's very very rare that an uncompressd object is larger than 4GB (partly because Git does not handle those large files very well to begin with). Let's optimize it for the common case where object size is smaller than this limit.
Shrink size field down to 32 bits [1] and one overflow bit. If the size is too large, we read it back from disk.
Add two compare helpers that can take advantage of the overflow bit (e.g. if the file is 4GB+, chances are it's already larger than core.bigFileThreshold and there's no point in comparing the actual value).
There's no actual saving from this due to holes. Which should be gone in the next patch.
[1] it's actually already 32 bits on 64-bit Windows
--- builtin/pack-objects.c | 49 ++++++++++++++++++++++++++---------------- pack-objects.h | 48 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 20 deletions(-)
builtin/pack-objects.c +31 -18
@@ -274,7 +274,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entif (!usable_delta) {if (entry->type ==OBJ_BLOB && - entry->size > big_file_threshold && +oe_size_greater_than (entry, big_file_threshold) && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;else {
@@ -384,12 +384,13 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; +unsigned long entry_size =oe_size (entry);if (DELTA (entry)) type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), - type, entry->size); + type, entry_size); offset = entry->in_pack_offset; revidx =find_pack_revindex (p, offset);
@@ -406,7 +407,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size;if (!pack_to_stdout && p->index_version ==1 && -check_pack_inflate (p, &w_curs, offset, datalen, entry->size)) { +check_pack_inflate (p, &w_curs, offset, datalen, entry_size)) {error ("corrupt packed object for %s" ,oid_to_hex (&entry->idx.oid));unuse_pack (&w_curs);
@@ -1412,6 +1413,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { +unsigned long size; +if (IN_PACK (entry)) {struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;
@@ -1431,13 +1434,14 @@ static void check_object(struct object_entry *entry)*/ used =unpack_object_header_buffer (buf, avail, &type, - &entry->size); + &size);if (used ==0 ) goto give_up;if (type <0 )die ("BUG: invalid type %d" , type); entry->in_pack_type = type; +oe_set_size (entry, size);/* * Determine if this is a delta and if so whether we can
@@ -1505,7 +1509,7 @@ static void check_object(struct object_entry *entry)*/ entry->type = entry->in_pack_type;SET_DELTA (entry, base_entry); - entry->delta_size = entry->size; + entry->delta_size =oe_size (entry); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1513,14 +1517,17 @@ static void check_object(struct object_entry *entry) }if (entry->type) { +unsigned long size; + + size =get_size_from_delta (p, &w_curs, + entry->in_pack_offset + entry->in_pack_header_size);/* * This must be a delta and we already know what the * final object type is. Let's extract the actual * object size from the delta header. */ - entry->size =get_size_from_delta (p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); -if (entry->size ==0 ) +oe_set_size (entry, size); +if (oe_size_less_than (entry,1 )) goto give_up;unuse_pack (&w_curs);return ;
@@ -1535,13 +1542,14 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); + entry->type =sha1_object_info (entry->idx.oid.hash, &size);/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored * as a preferred base. Doing so can result in a larger * pack file, but the transfer will still take place. */ +oe_set_size (entry, size); }static int pack_offset_sort (const void *_a,const void *_b)
@@ -1581,6 +1589,7 @@ static void drop_reused_delta(struct object_entry *entry)unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; +unsigned long size;while (*idx) {struct object_entry *oe = &to_pack.objects[*idx -1 ];
@@ -1593,7 +1602,7 @@ static void drop_reused_delta(struct object_entry *entry)SET_DELTA (entry,NULL ); entry->depth =0 ; - oi.sizep = &entry->size; + oi.sizep = &size; oi.typep = &type;if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/*
@@ -1603,11 +1612,13 @@ static void drop_reused_delta(struct object_entry *entry)* and dealt with in prepare_pack(). */ entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); + &size); +oe_set_size (entry, size); }else {if (type <0 )die ("BUG: invalid type %d" , type); entry->type = type; +oe_set_size (entry, size); } }
@@ -1748,7 +1759,7 @@ static void get_object_details(void)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = sorted_by_offset[i];check_object (entry); -if (big_file_threshold < entry->size) +if (oe_size_greater_than (entry, big_file_threshold)) entry->no_try_delta =1 ; }
@@ -1775,6 +1786,8 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +unsigned long a_size =oe_size (a); +unsigned long b_size =oe_size (b);if (a->type > b->type)return -1 ;
@@ -1788,9 +1801,9 @@ static int type_size_sort(const void *_a, const void *_b)return -1 ;if (a->preferred_base < b->preferred_base)return 1 ; -if (a->size > b->size) +if (a_size > b_size)return -1 ; -if (a->size < b->size) +if (a_size < b_size)return 1 ;return a < b ?-1 : (a > b);/* newest first */ }
@@ -1877,7 +1890,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,return 0 ;/* Now some size filtering heuristics. */ - trg_size = trg_entry->size; + trg_size =oe_size (trg_entry);if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ;
@@ -1889,7 +1902,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, (max_depth - ref_depth +1 );if (max_size ==0 )return 0 ; - src_size = src_entry->size; + src_size =oe_size (src_entry); sizediff = src_size < trg_size ? trg_size - src_size :0 ;if (sizediff >= max_size)return 0 ;
@@ -2009,7 +2022,7 @@ static unsigned long free_unpacked(struct unpacked *n)free_delta_index (n->index); n->index =NULL ;if (n->data) { - freed_mem += n->entry->size; + freed_mem +=oe_size (n->entry);FREE_AND_NULL (n->data); } n->entry =NULL ;
@@ -2459,7 +2472,7 @@ static void prepare_pack(int window, int depth)*/ continue ; -if (entry->size <50 ) +if (oe_size_less_than (entry,50 ))continue ;if (entry->no_try_delta)
pack-objects.h +46 -2
70 lines not on this page · raw message
Signature · 1 line
2.16.2.873.g32ff258c87
PATCH v3 12/12 pack-objects: shrink delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyAllowing a delta size of 64 bits is crazy. Shrink this field down to 31 bits with one overflow bit.
If we encounter an existing delta larger than 2GB, we do not cache delta_size at all and will get the value from oe_size(), potentially from disk if it's larger than 4GB.
--- builtin/pack-objects.c | 24 ++++++++++++++---------- pack-objects.h | 30 +++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 15 deletions(-)
builtin/pack-objects.c +14 -10
@@ -30,10 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA_SIZE (obj) oe_delta_size(&to_pack, obj)#define DELTA (obj) oe_delta(&to_pack, obj)#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj)#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj)#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_SIZE (obj, val) oe_set_delta_size(&to_pack, obj, val)#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val)#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)
@@ -140,7 +142,7 @@ static void *get_delta(struct object_entry *entry) oid_to_hex(&DELTA(entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 ); -if (!delta_buf || delta_size != entry->delta_size) +if (!delta_buf || delta_size !=DELTA_SIZE (entry))die ("delta size changed" );free (buf);free (base_buf);
@@ -291,14 +293,14 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entFREE_AND_NULL (entry->delta_data); entry->z_delta_size =0 ; }else if (entry->delta_data) { - size = entry->delta_size; + size =DELTA_SIZE (entry); buf = entry->delta_data; entry->delta_data =NULL ; type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); - size = entry->delta_size; + size =DELTA_SIZE (entry); type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -1509,7 +1511,7 @@ static void check_object(struct object_entry *entry)*/ entry->type = entry->in_pack_type;SET_DELTA (entry, base_entry); - entry->delta_size =oe_size (entry); +SET_DELTA_SIZE (entry,oe_size (entry)); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1895,7 +1897,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, max_size = trg_size/2 -20 ; ref_depth =1 ; }else { - max_size = trg_entry->delta_size; + max_size =DELTA_SIZE (trg_entry); ref_depth = trg->depth; } max_size = (uint64_t)max_size * (max_depth - src->depth) /
@@ -1966,10 +1968,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, delta_buf =create_delta (src->index, trg->data, trg_size, &delta_size, max_size);if (!delta_buf)return 0 ; +if (delta_size >=maximum_unsigned_value_of_type (uint32_t)) +return 0 ;if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ -if (delta_size == trg_entry->delta_size && +if (delta_size ==DELTA_SIZE (trg_entry) && src->depth +1 >= trg->depth) {free (delta_buf);return 0 ;
@@ -1984,7 +1988,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (trg_entry->delta_data);cache_lock ();if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; + delta_cache_size -=DELTA_SIZE (trg_entry); trg_entry->delta_data =NULL ; }if (delta_cacheable (src_size, trg_size, delta_size)) {
@@ -1997,7 +2001,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, }SET_DELTA (trg_entry, src_entry); - trg_entry->delta_size = delta_size; +SET_DELTA_SIZE (trg_entry, delta_size); trg->depth = src->depth +1 ;return 1 ;
@@ -2120,11 +2124,11 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,if (entry->delta_data && !pack_to_stdout) {unsigned long size; - size =do_compress (&entry->delta_data, entry->delta_size); + size =do_compress (&entry->delta_data,DELTA_SIZE (entry)); entry->z_delta_size = size;if (entry->z_delta_size == size) {cache_lock (); - delta_cache_size -= entry->delta_size; + delta_cache_size -=DELTA_SIZE (entry); delta_cache_size += entry->z_delta_size;cache_unlock (); }else {
pack-objects.h +25 -5
@@ -5,6 +5,7 @@#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14#define OE_Z_DELTA_BITS 16 +#define OE_DELTA_SIZE_BITS 31/* * State flags for depth-first search used for analyzing delta cycles.
@@ -73,9 +74,6 @@ struct object_entry {struct pack_idx_entry idx;/* object uncompressed size _if_ size_valid is true */ uint32_t size_; - -/* XXX 4 bytes hole, try to pack */ -off_t in_pack_offset;uint32_t delta_idx;/* delta base object */ uint32_t delta_child_idx;/* deltified objects who bases me */
@@ -84,7 +82,10 @@ struct object_entry {*/ uint32_t hash;/* name hint hash */ void *delta_data;/* cached delta (uncompressed) */ -unsigned long delta_size;/* delta data size (uncompressed) */ +/* object uncompressed size _if_ size_valid is true */ +uint32_t size; + uint32_t delta_size_:OE_DELTA_SIZE_BITS; /* delta data size (uncompressed) */ + uint32_t delta_size_valid:1;unsigned char in_pack_header_size;/* note: spare bits available! */ unsigned in_pack_idx:OE_IN_PACK_BITS; /* already in pack */ unsigned type:TYPE_BITS;
@@ -109,7 +110,7 @@ struct object_entry {*/ unsigned z_delta_size:OE_Z_DELTA_BITS; -/* size: 88, bit_padding: 1 bits */ +/* size: 80, bit_padding: 1 bits */ };struct packing_data {
@@ -296,4 +297,23 @@ static inline void oe_set_size(struct object_entry *e, e->size_valid = e->size_ == size; } +static inline unsigned long oe_delta_size (struct packing_data *pack, +const struct object_entry *e) +{ +if (e->delta_size_valid) +return e->delta_size_; +return oe_size (e); +} + +static inline void oe_set_delta_size (struct packing_data *pack, +struct object_entry *e, +unsigned long size) +{ + e->delta_size_ = size; + e->delta_size_valid =e->delta_size_ == size; +if (!e->delta_size_valid && size !=oe_size (e)) +die ("BUG: this can only happen in check_object() " +"where delta size is the same as entry size" ); +} +#endif
Signature · 1 line
2.16.2.873.g32ff258c87
v4 cover nd/pack-objects-pack-struct updates
In reply to Nguyễn Thái Ngọc DuyThe most important change in v4 is it fixes a case where I failed to propagate an error condition to later code in 02/11. This results in new wrappers, oe_type() and oe_set_type(). This also reveals another extra object type, OBJ_NONE, that's also used by pack-objects.
Other changes are comments fixes, commit messages fixes, off-by-one bugs. No more saving compared to v3.
I also changed my approach a bit. I stop trying to make struct reduction visible at every patch. All these patches shrink some field even if the struct size is the same. The reordering and packing happens at the last patch.
I'm not super happy that many corner cases of my changes are not covered by the test suite. In many cases it's very hard or expensive to create the right error condition. If only this code is part of libgit.a and I could write C unit tests for it...
Interdiff
-- 8< --
builtin/pack-objects.c +29 -29
@@ -275,7 +275,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstruct git_istream *st =NULL ;if (!usable_delta) { -if (entry->type ==OBJ_BLOB && +if (oe_type (entry) ==OBJ_BLOB &&oe_size_greater_than (entry, big_file_threshold) && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;
@@ -381,7 +381,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset; -enum object_type type = entry->type; +enum object_type type =oe_type (entry);off_t datalen;unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];
@@ -491,11 +491,12 @@ static off_t write_object(struct hashfile *f, to_reuse =0 ;/* explicit */ else if (!IN_PACK(entry)) to_reuse =0 ;/* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA)/* check_object() decided it for us ... */ to_reuse = usable_delta;/* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */
@@ -716,8 +717,8 @@ static struct object_entry **compute_write_order(void)* And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_COMMIT && - objects[i].type !=OBJ_TAG ) +if (oe_type (&objects[i]) !=OBJ_COMMIT && +oe_type (&objects[i]) !=OBJ_TAG )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -726,7 +727,7 @@ static struct object_entry **compute_write_order(void)* And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_TREE ) +if (oe_type (&objects[i]) !=OBJ_TREE )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -1083,8 +1084,7 @@ static void create_object_entry(const struct object_id *oid, entry =packlist_alloc (&to_pack, oid->hash, index_pos); entry->hash = hash; -if (type) - entry->type = type; +oe_set_type (entry, type);if (exclude) entry->preferred_base =1 ;else
@@ -1453,9 +1453,9 @@ static void check_object(struct object_entry *entry)switch (entry->in_pack_type) {default :/* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->in_pack_header_size = used; -if (entry->type <OBJ_COMMIT || entry->type >OBJ_BLOB ) +if (oe_type (entry) <OBJ_COMMIT ||oe_type (entry) >OBJ_BLOB ) goto give_up;unuse_pack (&w_curs);return ;
@@ -1509,7 +1509,7 @@ static void check_object(struct object_entry *entry)* deltify other objects against, in order to avoid * circular deltas. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry);SET_DELTA_SIZE (entry,oe_size (entry)); entry->delta_sibling_idx = base_entry->delta_child_idx;
@@ -1518,7 +1518,7 @@ static void check_object(struct object_entry *entry)return ; } -if (entry->type) { +if (oe_type (entry)) {unsigned long size; size = get_size_from_delta(p, &w_curs,
@@ -1544,14 +1544,15 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored * as a preferred base. Doing so can result in a larger * pack file, but the transfer will still take place. */ -oe_set_size (entry, size); +if (entry->type_valid) +oe_set_size (entry, size); }static int pack_offset_sort (const void *_a,const void *_b)
@@ -1613,15 +1614,12 @@ static void drop_reused_delta(struct object_entry *entry)* And if that fails, the error will be recorded in entry->type * and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &size); -oe_set_size (entry, size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &size)); }else { -if (type <0 ) -die ("BUG: invalid type %d" , type); - entry->type = type; -oe_set_size (entry, size); +oe_set_type (entry, type); } +oe_set_size (entry, size); }/*
@@ -1788,12 +1786,14 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +enum object_type a_type =oe_type (a); +enum object_type b_type =oe_type (b);unsigned long a_size =oe_size (a);unsigned long b_size =oe_size (b); -if (a->type > b->type) +if (a_type > b_type)return -1 ; -if (a->type < b->type) +if (a_type < b_type)return 1 ;if (a->hash > b->hash)return -1 ;
@@ -1869,7 +1869,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,void *delta_buf;/* Don't bother doing diffs between different types */ -if (trg_entry->type != src_entry->type) +if (oe_type (trg_entry) !=oe_type (src_entry))return -1 ;/*
@@ -2484,11 +2484,11 @@ static void prepare_pack(int window, int depth)if (!entry->preferred_base) { nr_deltas++; -if (entry->type <0 ) +if (oe_type (entry) <0 )die ("unable to get type of object %s" ,oid_to_hex (&entry->idx.oid)); }else { -if (entry->type <0 ) { +if (oe_type (entry) <0 ) {/* * This object is not found, but we * don't have to include it anyway.
@@ -2597,7 +2597,7 @@ static void read_object_list_from_stdin(void)die ("expected object ID, got garbage:\n %s" , line);add_preferred_base_object (p +1 ); -add_object_entry (&oid,0 , p +1 ,0 ); +add_object_entry (&oid,OBJ_NONE , p +1 ,0 ); } }
@@ -3110,7 +3110,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); -if (depth > (1 <<OE_DEPTH_BITS )) +if (depth >= (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), depth, (1 <<OE_DEPTH_BITS ));if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS ))
pack-bitmap-write.c +3 -3
@@ -66,12 +66,12 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,oe_set_in_pack_pos (to_pack, entry, i); -switch (entry->type) { +switch (oe_type (entry)) {case OBJ_COMMIT :case OBJ_TREE :case OBJ_BLOB :case OBJ_TAG : - real_type = entry->type; + real_type =oe_type (entry);break ;default :
@@ -100,7 +100,7 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,default :die ("Missing type information for %s (%d/%d)" ,oid_to_hex (&entry->idx.oid), real_type, - entry->type); +oe_type (entry)); } } }
pack-objects.h +28 -22
93 lines not on this page · raw message
-- 8< --
Nguyễn Thái Ngọc Duy (11):
pack-objects: a bit of document about struct object_entry
pack-objects: turn type and in_pack_type to bitfields
pack-objects: use bitfield for object_entry::dfs_state
pack-objects: use bitfield for object_entry::depth
pack-objects: move in_pack_pos out of struct object_entry
pack-objects: move in_pack out of struct object_entry
pack-objects: refer to delta objects by index instead of pointer
pack-objects: shrink z_delta_size field in struct object_entry
pack-objects: shrink size field in struct object_entry
pack-objects: shrink delta_size field in struct object_entry pack-objects.h: reorder members to shrink struct object_entry
Documentation/config.txt | 4 +- Documentation/git-pack-objects.txt | 13 +- Documentation/git-repack.txt | 4 +- builtin/pack-objects.c | 309 +++++++++++++++++------------ cache.h | 3 + object.h | 1 - pack-bitmap-write.c | 14 +- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +- pack-objects.h | 294 ++++++++++++++++++++++++--- 10 files changed, 488 insertions(+), 160 deletions(-)
Signature · 1 line
2.16.2.903.gd04caf5039
PATCH v4 1/11 pack-objects: a bit of document about struct object_entry
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what.
--- pack-objects.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+)
pack-objects.h +44 -0
@@ -1,6 +1,50 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* + * basic object info + * ----------------- + * idx.oid is filled up before delta searching starts. idx.crc32 and + * is only valid after the object is written out and will be used for + * generating the index. idx.offset will be both gradually set and + * used in writing phase (base objects get offset first, then deltas + * refer to them) + * + * "size" is the uncompressed object size. Compressed size is not + * cached (ie. raw data in a pack) but available via revindex. + * + * "hash" contains a path name hash which is used for sorting the + * delta list and also during delta searching. Once prepare_pack() + * returns it's no longer needed. + * + * source pack info + * ---------------- + * The (in_pack, in_pack_offset, in_pack_header_size) tuple contains + * the location of the object in the source pack, with or without + * header. + * + * "type" and "in_pack_type" both describe object type. in_pack_type + * may contain a delta type, while type is always the canonical type. + * + * deltas + * ------ + * Delta links (delta, delta_child and delta_sibling) are created + * reflect that delta graph from the source pack then updated or added + * during delta searching phase when we find better deltas. + * + * delta_child and delta_sibling are last needed in + * compute_write_order(). "delta" and "delta_size" must remain valid + * at object writing phase in case the delta is not cached. + * + * If a delta is cached in memory and is compressed delta_data points + * to the data and z_delta_size contains the compressed size. If it's + * uncompressed [1], z_delta_size must be zero. delta_size is always + * the uncompressed size and must be valid even if the delta is not + * cached. + * + * [1] during try_delta phase we don't bother with compressing because + * the delta could be quickly replaced with a better one. + */ struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 01/11] pack-objects: a bit of document about struct object_entry
Quoted 14 lines · Nguyễn Thái Ngọc Duy
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- pack-objects.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+)pack-objects.h +0
-0
pack-objects.h line 8
Quoted 8 lines
pack-objects.h +5
-0@@ -1,6 +1,50 @@#ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* +* basic object info +* ----------------- +* idx.oid is filled up before delta searching starts. idx.crc32 and +* is only valid after the object is written out and will be used for
"and is"?
pack-objects.h line 14
Quoted 6 lines
pack-objects.h +6
-0+* generating the index. idx.offset will be both gradually set and +* used in writing phase (base objects get offset first, then deltas +* refer to them) +* +* "size" is the uncompressed object size. Compressed size is not +* cached (ie. raw data in a pack) but available via revindex.
I am having a hard time understanding what "ie. raw data in a pack" is doing in that sentence.
It is correct that compressed size is not cached; it does not even exist and the only way to know it is to compute it by reversing the .idx file (or actually uncompressing the compressed stream).
Perhaps:
Compressed size of the raw data for an object in a pack is not stored anywhere but is computed and made available when reverse .idx is made.
pack-objects.h line 18
Quoted 3 lines
pack-objects.h +3
-0+* "hash" contains a path name hash which is used for sorting the +* delta list and also during delta searching. Once prepare_pack() +* returns it's no longer needed.
Hmm, that suggests an interesting optimization opportunity ;-)
pack-objects.h line 24
Quoted 5 lines
pack-objects.h +5
-0+* source pack info +* ---------------- +* The (in_pack, in_pack_offset, in_pack_header_size) tuple contains +* the location of the object in the source pack, with or without +* header.
"with or without", meaning...? An object in the source pack may or may not have any in_pack_header, in which case in_pack_header_size is zero, or something? Not suggesting to rephrase (at least not yet), but trying to understand.
pack-objects.h line 33
Quoted 8 lines
pack-objects.h +8
-0+* "type" and "in_pack_type" both describe object type. in_pack_type +* may contain a delta type, while type is always the canonical type. +* +* deltas +* ------ +* Delta links (delta, delta_child and delta_sibling) are created +* reflect that delta graph from the source pack then updated or added +* during delta searching phase when we find better deltas.
Isn't anything missing after "are created"? Perhaps "to"?
pack-objects.h line 37
Quoted 4 lines
pack-objects.h +4
-0+* +* delta_child and delta_sibling are last needed in +* compute_write_order(). "delta" and "delta_size" must remain valid +* at object writing phase in case the delta is not cached.
True. I thought child and sibling are only needed during write order computing, so there may be an optimization opportunity there.
pack-objects.h line 39
Quoted 1 line
pack-objects.h +1
-0+* If a delta is cached in memory and is compressed delta_data points
s/compressed delta_data/compressed, delta_data/;
Quoted 11 lines
pack-objects.h +8
-0+* to the data and z_delta_size contains the compressed size. If it's +* uncompressed [1], z_delta_size must be zero. delta_size is always +* the uncompressed size and must be valid even if the delta is not +* cached. +* +* [1] during try_delta phase we don't bother with compressing because +* the delta could be quickly replaced with a better one. +*/ struct object_entry {struct pack_idx_entry idx;unsigned long size;/* uncompressed size */
Re: [PATCH v4 01/11] pack-objects: a bit of document about struct object_entry
Quoted 7 lines · Junio C Hamano
Quoted 5 lines
+/*
+ * basic object info
+ * -----------------
+ * idx.oid is filled up before delta searching starts. idx.crc32 and
+ * is only valid after the object is written out and will be used for"and is"?
There was another field that I thought was only valid after blah blah. But it was wrong and I forgot to delete this "and" after deleting that field.
Quoted 5 lines
Quoted 3 lines
+ * "hash" contains a path name hash which is used for sorting the
+ * delta list and also during delta searching. Onceprepare_pack()
+ * returns it's no longer needed.Hmm, that suggests an interesting optimization opportunity ;-)
Heh.. it does not reduce peak memory consumption though which is why I'm less interested in freeing it after prepare_pack().
Quoted 10 lines
Quoted 5 lines
+ * source pack info
+ * ----------------
+ * The (in_pack,in_pack_offset,in_pack_header_size) tuple contains
+ * the location of the object in the source pack, with or without
+ * header."with or without", meaning...? An object in the source pack may or may not have any
in_pack_header, in which casein_pack_header_sizeis zero, or something? Not suggesting to rephrase (at least not yet), but trying to understand.
The location with the header (i.e. true beginning an object in a pack) or without/after the header so you are at the zlib stream, ready to inflate or reuse. I'll rephrase this a bit.
Quoted 7 lines
Quoted 4 lines
+ *
+ *delta_childanddelta_siblingare last needed in
+ *compute_write_order(). "delta" and "delta_size" must remain valid
+ * at object writing phase in case the delta is not cached.True. I thought child and sibling are only needed during write order computing, so there may be an optimization opportunity there.
See. I wrote all this for a reason. Somebody looking for low hang fruit can always find some ;-)
Signature · 1 line
Duy
PATCH v4 2/11 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyAn extra field type_valid is added to carry the equivalent of OBJ_BAD in the original "type" field. in_pack_type always contains a valid type so we only need 3 bits for it.
A note about accepting OBJ_NONE as "valid" type. The function read_object_list_from_stdin() can pass this value [1] and it eventually calls create_object_entry() where current code skip setting "type" field if the incoming type is zero. This does not have any bad side effects because "type" field should be memset()'d anyway.
But since we also need to set type_valid now, skipping oe_set_type() leaves type_valid zero/false, which will make oe_type() return OBJ_BAD, not OBJ_NONE anymore. Apparently we do care about OBJ_NONE in prepare_pack(). This switch from OBJ_NONE to OBJ_BAD may trigger
fatal: unable to get type of object ...
Accepting OBJ_NONE [2] does sound wrong, but this is how it is has been for a very long time and I haven't time to dig in further.
[1] See 5c49c11686 (pack-objects: better check_object() performances -
2007-04-16)
[2] 21666f1aae (convert object type handling from a string to a number
- 2007-02-26)
--- builtin/pack-objects.c | 58 +++++++++++++++++++++++++----------------- cache.h | 2 ++ object.h | 1 - pack-bitmap-write.c | 6 ++--- pack-objects.h | 20 +++++++++++++-- 5 files changed, 57 insertions(+), 30 deletions(-)
builtin/pack-objects.c +34 -24
@@ -265,7 +265,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstruct git_istream *st =NULL ;if (!usable_delta) { -if (entry->type ==OBJ_BLOB && +if (oe_type (entry) ==OBJ_BLOB && entry->size > big_file_threshold && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;
@@ -371,7 +371,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset; -enum object_type type = entry->type; +enum object_type type =oe_type (entry);off_t datalen;unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];
@@ -480,11 +480,12 @@ static off_t write_object(struct hashfile *f, to_reuse =0 ;/* explicit */ else if (!entry->in_pack) to_reuse =0 ;/* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA)/* check_object() decided it for us ... */ to_reuse = usable_delta;/* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ else if (entry->delta) to_reuse =0 ;/* we want to pack afresh */
@@ -705,8 +706,8 @@ static struct object_entry **compute_write_order(void)* And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_COMMIT && - objects[i].type !=OBJ_TAG ) +if (oe_type (&objects[i]) !=OBJ_COMMIT && +oe_type (&objects[i]) !=OBJ_TAG )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -715,7 +716,7 @@ static struct object_entry **compute_write_order(void)* And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_TREE ) +if (oe_type (&objects[i]) !=OBJ_TREE )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -1066,8 +1067,7 @@ static void create_object_entry(const struct object_id *oid, entry =packlist_alloc (&to_pack, oid->hash, index_pos); entry->hash = hash; -if (type) - entry->type = type; +oe_set_type (entry, type);if (exclude) entry->preferred_base =1 ;else
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1428,9 +1433,9 @@ static void check_object(struct object_entry *entry)switch (entry->in_pack_type) {default :/* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->in_pack_header_size = used; -if (entry->type <OBJ_COMMIT || entry->type >OBJ_BLOB ) +if (oe_type (entry) <OBJ_COMMIT ||oe_type (entry) >OBJ_BLOB ) goto give_up;unuse_pack (&w_curs);return ;
@@ -1484,7 +1489,7 @@ static void check_object(struct object_entry *entry)* deltify other objects against, in order to avoid * circular deltas. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->delta = base_entry; entry->delta_size = entry->size; entry->delta_sibling = base_entry->delta_child;
@@ -1493,7 +1498,7 @@ static void check_object(struct object_entry *entry)return ; } -if (entry->type) { +if (oe_type (entry)) {/* * This must be a delta and we already know what the * final object type is. Let's extract the actual
@@ -1516,7 +1521,7 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
@@ -1578,8 +1584,10 @@ static void drop_reused_delta(struct object_entry *entry)* And if that fails, the error will be recorded in entry->type * and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &entry->size)); + }else { +oe_set_type (entry, type); } }
@@ -1747,10 +1755,12 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +enum object_type a_type =oe_type (a); +enum object_type b_type =oe_type (b); -if (a->type > b->type) +if (a_type > b_type)return -1 ; -if (a->type < b->type) +if (a_type < b_type)return 1 ;if (a->hash > b->hash)return -1 ;
@@ -1826,7 +1836,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,void *delta_buf;/* Don't bother doing diffs between different types */ -if (trg_entry->type != src_entry->type) +if (oe_type (trg_entry) !=oe_type (src_entry))return -1 ;/*
@@ -2432,11 +2442,11 @@ static void prepare_pack(int window, int depth)if (!entry->preferred_base) { nr_deltas++; -if (entry->type <0 ) +if (oe_type (entry) <0 )die ("unable to get type of object %s" ,oid_to_hex (&entry->idx.oid)); }else { -if (entry->type <0 ) { +if (oe_type (entry) <0 ) {/* * This object is not found, but we * don't have to include it anyway.
@@ -2545,7 +2555,7 @@ static void read_object_list_from_stdin(void)die ("expected object ID, got garbage:\n %s" , line);add_preferred_base_object (p +1 ); -add_object_entry (&oid,0 , p +1 ,0 ); +add_object_entry (&oid,OBJ_NONE , p +1 ,0 ); } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-bitmap-write.c +3 -3
@@ -64,12 +64,12 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, entry->in_pack_pos = i; -switch (entry->type) { +switch (oe_type (entry)) {case OBJ_COMMIT :case OBJ_TREE :case OBJ_BLOB :case OBJ_TAG : - real_type = entry->type; + real_type =oe_type (entry);break ;default :
@@ -98,7 +98,7 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,default :die ("Missing type information for %s (%d/%d)" ,oid_to_hex (&entry->idx.oid), real_type, - entry->type); +oe_type (entry)); } } }
pack-objects.h +18 -2
@@ -58,8 +58,9 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ + unsigned type_:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ + unsigned type_valid:1;uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size;
@@ -122,4 +123,19 @@ static inline uint32_t pack_name_hash(const char *name) return hash; } +static inline enum object_type oe_type (const struct object_entry *e) +{ +return e->type_valid ? e->type_ :OBJ_BAD ; +} + +static inline void oe_set_type (struct object_entry *e, +enum object_type type) +{ +if (type >=OBJ_ANY ) +die ("BUG: OBJ_ANY cannot be set in pack-objects code" ); + + e->type_valid = type >=OBJ_NONE ; + e->type_ = (unsigned )type; +} +#endif
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 02/11] pack-objects: turn type and in_pack_type to bitfields
Quoted 14 lines · Nguyễn Thái Ngọc Duy
An extra field
type_validis added to carry the equivalent ofOBJ_BADin the original "type" field.in_pack_typealways contains a valid type so we only need 3 bits for it.
...builtin/pack-objects.c +1
-1@@ -1570,7 +1576,7 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason;
builtin/pack-objects.c static void drop_reused_delta(struct object_entry *entry) · line 1584
Quoted 1 line
builtin/pack-objects.c +0
-0@@ -1578,8 +1584,10 @@ static void drop_reused_delta(struct object_entry *entry)* And if that fails, the error will be recorded in entry->type
This "entry->type" needs updating.
Quoted 10 lines
builtin/pack-objects.c +4
-2* and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &entry->size)); + }else { +oe_set_type (entry, type); } }
PATCH v4 3/11 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +17 -11
@@ -1,6 +1,21 @@#ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* +* State flags for depth-first search used for analyzing delta cycles. +* +* The depth is measured in delta-links to the base (so if A is a delta +* against B, then A has a depth of 1, and B a depth of 0). +*/ +enum dfs_state { +DFS_NONE =0 , +DFS_ACTIVE , +DFS_DONE , +DFS_NUM_STATES +}; +/* * basic object info * -----------------
@@ -72,19 +87,10 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; + };struct packing_data {
Signature · 1 line
2.16.2.903.gd04caf5039
PATCH v4 4/11 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyBecause of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 4 +++- Documentation/git-repack.txt | 4 +++- builtin/pack-objects.c | 4 ++++ pack-objects.h | 5 ++--- 5 files changed, 13 insertions(+), 5 deletions(-)
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +3 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth >= (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +2 -3
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -88,9 +89,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; - -int depth; - + unsigned depth:OE_DEPTH_BITS; };struct packing_data {
Signature · 1 line
2.16.2.903.gd04caf5039
PATCH v4 5/11 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not).
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 7 deletions(-)
builtin/pack-objects.c +2 -1
@@ -879,7 +879,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +oe_set_in_pack_pos (to_pack, entry, i);switch (oe_type (entry)) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return oe_in_pack_pos (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =oe_in_pack_pos (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +15 -1
@@ -78,7 +78,6 @@ struct object_entry { unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; unsigned preferred_base:1; /** we do not pack this, but is available
@@ -98,6 +97,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
@@ -143,4 +144,17 @@ static inline void oe_set_type(struct object_entry *e, e->type_ = (unsigned)type; } +static inline unsigned int oe_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e) +{ +return pack->in_pack_pos[e - pack->objects]; +} + +static inline void oe_set_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e, +unsigned int pos) +{ + pack->in_pack_pos[e - pack->objects] = pos; +} +#endif
Signature · 1 line
2.16.2.903.gd04caf5039
PATCH v4 6/11 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index instead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
--- Documentation/git-pack-objects.txt | 9 ++++++ builtin/pack-objects.c | 40 +++++++++++++++++---------- cache.h | 1 + pack-objects.h | 44 +++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 15 deletions(-)
Documentation/git-pack-objects.txt +9 -0
@@ -269,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
builtin/pack-objects.c +26 -14
@@ -29,6 +29,8 @@#include "list.h" #include "packfile.h" +#define IN_PACK (obj) oe_in_pack(&to_pack, obj) +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -367,7 +369,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p = entry->in_pack; +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -478,7 +480,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!entry->in_pack) +else if (!IN_PACK (entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (oe_type (entry) ==OBJ_REF_DELTA ||oe_type (entry) ==OBJ_OFS_DELTA )
@@ -1025,7 +1027,7 @@ static int want_object_in_pack(const struct object_id *oid,if (*found_pack) { want =want_found_object (exclude, *found_pack);if (want !=-1 ) -return want; + goto done; }list_for_each (pos, &packed_git_mru) {
@@ -1048,11 +1050,16 @@ static int want_object_in_pack(const struct object_id *oid,if (!exclude && want >0 )list_move (&p->mru, &packed_git_mru);if (want !=-1 ) -return want; + goto done; } } -return 1 ; + want =1 ; +done: +if (want && *found_pack && !(*found_pack)->index) +oe_add_pack (&to_pack, *found_pack); + +return want; } static void create_object_entry(const struct object_id *oid,
@@ -1074,7 +1081,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { - entry->in_pack = found_pack; +oe_set_in_pack (entry, found_pack); entry->in_pack_offset = found_offset; }
@@ -1399,8 +1406,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (entry->in_pack) { -struct packed_git *p = entry->in_pack; +if (IN_PACK (entry)) { +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1535,14 +1542,16 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +const struct packed_git *a_in_pack =IN_PACK (a); +const struct packed_git *b_in_pack =IN_PACK (b);/* avoid filesystem trashing with loose objects */ -if (!a->in_pack && !b->in_pack) +if (!a_in_pack && !b_in_pack)return oidcmp (&a->idx.oid, &b->idx.oid); -if (a->in_pack < b->in_pack) +if (a_in_pack < b_in_pack)return -1 ; -if (a->in_pack > b->in_pack) +if (a_in_pack > b_in_pack)return 1 ;return a->in_pack_offset < b->in_pack_offset ?-1 : (a->in_pack_offset > b->in_pack_offset);
@@ -1578,7 +1587,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1848,8 +1857,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && +if (reuse_delta &&IN_PACK (trg_entry) && +IN_PACK (trg_entry) ==IN_PACK (src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -3191,6 +3200,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } +/* make sure IN_PACK(0) return NULL */ +oe_add_pack (&to_pack,NULL ); +if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );if (!use_internal_rev_list)
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
pack-objects.h +43 -1
72 lines not on this page · raw message
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 06/11] pack-objects: move in_pack out of struct object_entry
Hi,
sorry for the late review, as I am pointed here indirectly via
https://public-inbox.org/git/[email protected]/
Documentation/git-pack-objects.txt Unexpected missing object will raise an error. · line 275
Quoted 4 lines · Nguyễn Thái Ngọc Duy
Documentation/git-pack-objects.txt +4
-0+LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time.
s/could/can/ ?
Review int cmd_pack_objects(int argc, const char **argv,
Quoted 1 line
(quoted diff) +0
-0@@ -3191,6 +3200,9 @@ int cmd_pack_objects(int argc, const char **argv,
const char *prefix)
builtin/pack-objects.c int cmd_pack_objects(int argc, const char **argv, const char *prefix) · line 3203
Quoted 4 lines
builtin/pack-objects.c +1
-0} } +/* make sure IN_PACK(0) return NULL */
I was confused for a while staring at this comment, /s/0/NULL/ would have helped me.
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack, · line 175
Quoted 9 lines
pack-objects.h +9
-0+static inline unsigned int oe_add_pack (struct packing_data *pack, +struct packed_git *p) +{ +if (pack->in_pack_count >= (1 <<OE_IN_PACK_BITS )) +die (_ ("too many packs to handle in one go. " +"Please add .keep files to exclude\n" +"some pack files and keep the number " +"of non-kept files below %d." ), +1 <<OE_IN_PACK_BITS );
The packs are indexed 0..N-1, so we can actually handle N packs I presume. But if we actually have N, then we'd run the
/* make sure IN_PACK(0) return NULL */ oe_add_pack(.., NULL);
as N+1, hence the user can only do N-1 ?
Oh wait! the code below makes me think we index from 1..N, treating index 0 special as uninitialized? So we actually can only store N-1 ?
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack, · line 177
Quoted 2 lines
pack-objects.h +2
-0+if (p) { +if (p->index >0 )
s/>/!=/ ?
The new index variable is only used in these three inlined header functions, and in_pack_count is strictly positive, so index as well as in_pack_count could be made unsigned?
Given that oe_add_pack returns an unsigned, I would actually prefer to have in_pack_count an unsigned as well.
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack,
Quoted 8 lines
pack-objects.h +8
-0+die ("BUG: this packed is already indexed" ); + p->index = pack->in_pack_count; + } + pack->in_pack[pack->in_pack_count] = p; +return pack->in_pack_count++; +} + +static inline struct packed_git *oe_in_pack(const struct packing_data
*pack,
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack, · line 190
Quoted 5 lines
pack-objects.h +5
-0+ const struct object_entry *e) +{ +return pack->in_pack[e->in_pack_idx]; + +}
extra new line after return?
pack-objects.h static inline void oe_set_in_pack_pos(const struct packing_data *pack, · line 197
Quoted 6 lines
pack-objects.h +6
-0+static inline void oe_set_in_pack (struct object_entry *e, +struct packed_git *p) +{ +if (p->index <=0 ) +die ("BUG: found_pack should be NULL " +"instead of having non-positive index" );
Do we also want to guard against
p->index > (1 << OE_IN_PACK_BITS)
here? Also there is a BUG() macro, that would be better as it reports line file/number, but we cannot use it here as it is a header inline.
PATCH v4 7/11 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyThese delta pointers always point to elements in the objects[] array in packing_data struct. We can only hold maximum 4GB of those objects because the array length, nr_objects, is uint32_t. We could use uint32_t indexes to address these elements instead of pointers. On 64-bit architecture (8 bytes per pointer) this would save 4 bytes per pointer.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 116 ++++++++++++++++++++++------------------- pack-objects.h | 67 ++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 59 deletions(-)
builtin/pack-objects.c +62 -54
@@ -30,6 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA (obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),
@@ -127,11 +133,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -288,12 +294,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -317,7 +323,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -343,7 +349,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -379,8 +385,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -408,7 +414,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -427,7 +433,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -467,13 +473,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -489,7 +495,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -541,12 +547,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +SET_DELTA (e,NULL );break ;default :break ;
@@ -608,34 +614,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -646,7 +652,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -661,8 +667,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +SET_DELTA_CHILD (&objects[i],NULL ); +SET_DELTA_SIBLING (&objects[i],NULL ); }/*
@@ -672,11 +678,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1498,10 +1504,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ oe_set_type (entry, entry->in_pack_type); - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1572,17 +1578,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +SET_DELTA (entry,NULL ); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1622,7 +1630,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1647,7 +1655,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1670,7 +1678,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1685,7 +1693,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1870,7 +1878,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1946,7 +1954,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1975,7 +1983,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1984,13 +1992,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2059,7 +2067,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2109,7 +2117,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2117,7 +2125,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2438,7 +2446,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +62 -5
77 lines not on this page · raw message
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 07/11] pack-objects: refer to delta objects by index instead of pointer
Quoted 2 lines · Nguyễn Thái Ngọc Duy
These delta pointers always point to elements in the objects[] array in
packing_datastruct. We can only hold maximum 4GB of those objects
4GB, as in "number of bytes"? Or "We can hold 4 billion or so of those objects"?
Quoted 12 lines
because the array length,
nr_objects, isuint32_t. We could useuint32_tindexes to address these elements instead of pointers. On 64-bit architecture (8 bytes per pointer) this would save 4 bytes per pointer.Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
[1] This means we can only index 2^32-2 objects even though
nr_objectscould contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[],nr_allocwould probably blow up long beforenr_objectshits the wall.
PATCH v4 8/11 pack-objects: shrink z_delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyWe only cache deltas when it's smaller than a certain limit. This limit defaults to 1000 but save its compressed length in a 64-bit field. Shrink that field down to 16 bits, so you can only cache 65kb deltas. Larger deltas must be recomputed at when the pack is written down.
--- Documentation/config.txt | 3 ++- builtin/pack-objects.c | 22 ++++++++++++++++------ pack-objects.h | 3 ++- 3 files changed, 20 insertions(+), 8 deletions(-)
Documentation/config.txt +2 -1
@@ -2449,7 +2449,8 @@ pack.deltaCacheLimit:: The maximum size of a delta, that is cached in linkgit:git-pack-objects[1]. This cache is used to speed up the writing object phase by not having to recompute the final delta - result once the best match for all objects is found. Defaults to 1000. + result once the best match for all objects is found. + Defaults to 1000. Maximum value is 65535. pack.threads:: Specifies the number of threads to spawn when searching for best
builtin/pack-objects.c +16 -6
@@ -2105,12 +2105,19 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* between writes at that moment. */ if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size =do_compress (&entry->delta_data, - entry->delta_size); -cache_lock (); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; -cache_unlock (); +unsigned long size; + + size =do_compress (&entry->delta_data, entry->delta_size); + entry->z_delta_size = size; +if (entry->z_delta_size == size) { +cache_lock (); + delta_cache_size -= entry->delta_size; + delta_cache_size += entry->z_delta_size; +cache_unlock (); + }else { +FREE_AND_NULL (entry->delta_data); + entry->z_delta_size =0 ; + } }/* if we made n a delta, and if n is already at max
@@ -3089,6 +3096,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (depth >= (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), depth, (1 <<OE_DEPTH_BITS )); +if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS )) +die (_ ("pack.deltaCacheLimit is greater than maximum limit %d" ), +1 <<OE_Z_DELTA_BITS );argv_array_push (&rp,"pack-objects" );if (thin) {
pack-objects.h +2 -1
@@ -4,6 +4,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14 +#define OE_Z_DELTA_BITS 16/* * State flags for depth-first search used for analyzing delta cycles.
@@ -78,7 +79,7 @@ struct object_entry {*/ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ -unsigned long z_delta_size;/* delta data size (compressed) */ + unsigned z_delta_size:OE_Z_DELTA_BITS; unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 08/11] pack-objects: shrink z_delta_size field in struct object_entry
Quoted 10 lines · Nguyễn Thái Ngọc Duy
We only cache deltas when it's smaller than a certain limit. This limit defaults to 1000 but save its compressed length in a 64-bit field. Shrink that field down to 16 bits, so you can only cache 65kb deltas. Larger deltas must be recomputed at when the pack is written down.
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>---
builtin/pack-objects.c +0
-1if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size = do_compress(&entry->delta_data,
builtin/pack-objects.c static void find_deltas(struct object_entry **list, unsigned *list_size, · line 2112
Quoted 10 lines
builtin/pack-objects.c +5
-5- entry->delta_size); -cache_lock (); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; -cache_unlock (); +unsigned long size; + + size =do_compress (&entry->delta_data, entry->delta_size); + entry->z_delta_size = size; +if (entry->z_delta_size == size) {
It is confusing to readers to write
A = B;
if (A == B) {
/* OK, A was big enough */
} else {
/* No, B is too big to fit on A */
}I actually was about to complain that you attempted an unrelated micro-optimization to skip cache_lock/unlock when delta_size and z_delta_size are the same, and made a typo. Something like:
size = do_compress(...);
if (size < (1 << OE_Z_DELTA_BITS)) {
entry->z_delta_size = size;
cache_lock();
...
cache_unlock();
} else {
FREE_AND_NULL(entry->delta_data);
entry->z_delta_size = 0;
}would have saved me a few dozens of seconds of head-scratching.
Quoted 9 lines
builtin/pack-objects.c +8
-0+cache_lock (); + delta_cache_size -= entry->delta_size; + delta_cache_size += entry->z_delta_size; +cache_unlock (); + }else { +FREE_AND_NULL (entry->delta_data); + entry->z_delta_size =0 ; + } }
PATCH v4 9/11 pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyIt's very very rare that an uncompressd object is larger than 4GB (partly because Git does not handle those large files very well to begin with). Let's optimize it for the common case where object size is smaller than this limit.
Shrink size field down to 32 bits [1] and one overflow bit. If the size is too large, we read it back from disk.
Add two compare helpers that can take advantage of the overflow bit (e.g. if the file is 4GB+, chances are it's already larger than core.bigFileThreshold and there's no point in comparing the actual value).
A small note about the conditional oe_set_size() in check_object(). Technically if we don't get a valid type, it's not wrong if we set uninitialized value "size" (we don't pre-initialize this and sha1_object_info will not assign anything when it fails to get the info).
This how changes the writing code path slightly which emits different error messages (either way we die). One of our tests in t5530 depends on this specific error message. Let's just keep the test as-is and play safe by not assigning random value. That might trigger valgrind anyway.
[1] it's actually already 32 bits on Windows
--- builtin/pack-objects.c | 49 ++++++++++++++++++++++++++---------------- pack-objects.h | 43 +++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 19 deletions(-)
builtin/pack-objects.c +31 -18
@@ -274,7 +274,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entif (!usable_delta) {if (oe_type (entry) ==OBJ_BLOB && - entry->size > big_file_threshold && +oe_size_greater_than (entry, big_file_threshold) && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;else {
@@ -384,12 +384,13 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; +unsigned long entry_size =oe_size (entry);if (DELTA (entry)) type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), - type, entry->size); + type, entry_size); offset = entry->in_pack_offset; revidx =find_pack_revindex (p, offset);
@@ -406,7 +407,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size;if (!pack_to_stdout && p->index_version ==1 && -check_pack_inflate (p, &w_curs, offset, datalen, entry->size)) { +check_pack_inflate (p, &w_curs, offset, datalen, entry_size)) {error ("corrupt packed object for %s" ,oid_to_hex (&entry->idx.oid));unuse_pack (&w_curs);
@@ -1412,6 +1413,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { +unsigned long size; +if (IN_PACK (entry)) {struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;
@@ -1431,13 +1434,14 @@ static void check_object(struct object_entry *entry)*/ used =unpack_object_header_buffer (buf, avail, &type, - &entry->size); + &size);if (used ==0 ) goto give_up;if (type <0 )die ("BUG: invalid type %d" , type); entry->in_pack_type = type; +oe_set_size (entry, size);/* * Determine if this is a delta and if so whether we can
@@ -1505,7 +1509,7 @@ static void check_object(struct object_entry *entry)*/ oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry); - entry->delta_size = entry->size; + entry->delta_size =oe_size (entry); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1513,14 +1517,17 @@ static void check_object(struct object_entry *entry) }if (oe_type (entry)) { +unsigned long size; + + size =get_size_from_delta (p, &w_curs, + entry->in_pack_offset + entry->in_pack_header_size);/* * This must be a delta and we already know what the * final object type is. Let's extract the actual * object size from the delta header. */ - entry->size =get_size_from_delta (p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); -if (entry->size ==0 ) +oe_set_size (entry, size); +if (oe_size_less_than (entry,1 )) goto give_up;unuse_pack (&w_curs);return ;
@@ -1535,13 +1542,15 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } -oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size)); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored * as a preferred base. Doing so can result in a larger * pack file, but the transfer will still take place. */ +if (entry->type_valid) +oe_set_size (entry, size); }static int pack_offset_sort (const void *_a,const void *_b)
@@ -1581,6 +1590,7 @@ static void drop_reused_delta(struct object_entry *entry)unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; +unsigned long size;while (*idx) {struct object_entry *oe = &to_pack.objects[*idx -1 ];
@@ -1593,7 +1603,7 @@ static void drop_reused_delta(struct object_entry *entry)SET_DELTA (entry,NULL ); entry->depth =0 ; - oi.sizep = &entry->size; + oi.sizep = &size; oi.typep = &type;if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/*
@@ -1603,10 +1613,11 @@ static void drop_reused_delta(struct object_entry *entry)* and dealt with in prepare_pack(). */ oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, - &entry->size)); + &size)); }else {oe_set_type (entry, type); } +oe_set_size (entry, size); }/*
@@ -1746,7 +1757,7 @@ static void get_object_details(void)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = sorted_by_offset[i];check_object (entry); -if (big_file_threshold < entry->size) +if (oe_size_greater_than (entry, big_file_threshold)) entry->no_try_delta =1 ; }
@@ -1775,6 +1786,8 @@ static int type_size_sort(const void *_a, const void *_b)const struct object_entry *b = *(struct object_entry **)_b;enum object_type a_type =oe_type (a);enum object_type b_type =oe_type (b); +unsigned long a_size =oe_size (a); +unsigned long b_size =oe_size (b);if (a_type > b_type)return -1 ;
@@ -1788,9 +1801,9 @@ static int type_size_sort(const void *_a, const void *_b)return -1 ;if (a->preferred_base < b->preferred_base)return 1 ; -if (a->size > b->size) +if (a_size > b_size)return -1 ; -if (a->size < b->size) +if (a_size < b_size)return 1 ;return a < b ?-1 : (a > b);/* newest first */ }
@@ -1877,7 +1890,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,return 0 ;/* Now some size filtering heuristics. */ - trg_size = trg_entry->size; + trg_size =oe_size (trg_entry);if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ;
@@ -1889,7 +1902,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, (max_depth - ref_depth +1 );if (max_size ==0 )return 0 ; - src_size = src_entry->size; + src_size =oe_size (src_entry); sizediff = src_size < trg_size ? trg_size - src_size :0 ;if (sizediff >= max_size)return 0 ;
@@ -2009,7 +2022,7 @@ static unsigned long free_unpacked(struct unpacked *n)free_delta_index (n->index); n->index =NULL ;if (n->data) { - freed_mem += n->entry->size; + freed_mem +=oe_size (n->entry);FREE_AND_NULL (n->data); } n->entry =NULL ;
@@ -2459,7 +2472,7 @@ static void prepare_pack(int window, int depth)*/ continue ; -if (entry->size <50 ) +if (oe_size_less_than (entry,50 ))continue ;if (entry->no_try_delta)
pack-objects.h +42 -1
53 lines not on this page · raw message
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 09/11] pack-objects: shrink size field in struct object_entry
Quoted 7 lines · Nguyễn Thái Ngọc Duy
It's very very rare that an uncompressd object is larger than 4GB (partly because Git does not handle those large files very well to begin with). Let's optimize it for the common case where object size is smaller than this limit.
Shrink size field down to 32 bits [1] and one overflow bit. If the size is too large, we read it back from disk.
OK.
Quoted 4 lines
Add two compare helpers that can take advantage of the overflow bit (e.g. if the file is 4GB+, chances are it's already larger than core.bigFileThreshold and there's no point in comparing the actual value).
I had trouble reading the callers of these helpers.
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 280
Quoted 7 lines
pack-objects.h +7
-0+static inline int oe_size_less_than (const struct object_entry *e, +unsigned long limit) +{ +if (e->size_valid) +return e->size_ < limit; +if (limit >maximum_unsigned_value_of_type (uint32_t)) +return 1 ;
When size_valid bit is false, that means that the size is larger than 4GB. If "limit" is larger than 4GB, then we do not know anything, no? I'd understand if this "optimization" were
if (limit < 4GB) {
/*
* we know e whose size won't fit in 4GB is larger
* than that!
*/
return 0;
}Review
Quoted 2 lines
(quoted diff) +2
-0+ return oe_size(e) < limit; +}
Also, don't we want to use uintmax_t throughout the callchain? How would the code in this series work when your ulong is 32-bit?
Quoted 19 lines
pack-objects.h +18
-0+ +static inline int oe_size_greater_than (const struct object_entry *e, +unsigned long limit) +{ +if (e->size_valid) +return e->size_ > limit; +if (limit <=maximum_unsigned_value_of_type (uint32_t)) +return 1 ; +return oe_size (e) > limit; +} + +static inline void oe_set_size (struct object_entry *e, +unsigned long size) +{ + e->size_ = size; + e->size_valid = e->size_ == size; +} +#endif
Re: [PATCH v4 09/11] pack-objects: shrink size field in struct object_entry
Quoted 2 lines · Junio C Hamano
Also, don't we want to use
uintmax_tthroughout the callchain? How would the code in this series work when your ulong is 32-bit?
My own answer to this question is "no conversion to uintmax_t, at least not in this series." As long as the original code uses "unsigned long", this series also should, I think.
PATCH v4 10/11 pack-objects: shrink delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyAllowing a delta size of 64 bits is crazy. Shrink this field down to 31 bits with one overflow bit.
If we find an existing delta larger than 2GB, we do not cache delta_size at all and will get the value from oe_size(), potentially from disk if it's larger than 4GB.
--- builtin/pack-objects.c | 24 ++++++++++++++---------- pack-objects.h | 23 ++++++++++++++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-)
builtin/pack-objects.c +14 -10
@@ -30,10 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA_SIZE (obj) oe_delta_size(&to_pack, obj)#define DELTA (obj) oe_delta(&to_pack, obj)#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj)#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj)#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_SIZE (obj, val) oe_set_delta_size(&to_pack, obj, val)#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val)#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)
@@ -140,7 +142,7 @@ static void *get_delta(struct object_entry *entry) oid_to_hex(&DELTA(entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 ); -if (!delta_buf || delta_size != entry->delta_size) +if (!delta_buf || delta_size !=DELTA_SIZE (entry))die ("delta size changed" );free (buf);free (base_buf);
@@ -291,14 +293,14 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entFREE_AND_NULL (entry->delta_data); entry->z_delta_size =0 ; }else if (entry->delta_data) { - size = entry->delta_size; + size =DELTA_SIZE (entry); buf = entry->delta_data; entry->delta_data =NULL ; type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); - size = entry->delta_size; + size =DELTA_SIZE (entry); type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -1509,7 +1511,7 @@ static void check_object(struct object_entry *entry)*/ oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry); - entry->delta_size =oe_size (entry); +SET_DELTA_SIZE (entry,oe_size (entry)); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1895,7 +1897,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, max_size = trg_size/2 -20 ; ref_depth =1 ; }else { - max_size = trg_entry->delta_size; + max_size =DELTA_SIZE (trg_entry); ref_depth = trg->depth; } max_size = (uint64_t)max_size * (max_depth - src->depth) /
@@ -1966,10 +1968,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, delta_buf =create_delta (src->index, trg->data, trg_size, &delta_size, max_size);if (!delta_buf)return 0 ; +if (delta_size >=maximum_unsigned_value_of_type (uint32_t)) +return 0 ;if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ -if (delta_size == trg_entry->delta_size && +if (delta_size ==DELTA_SIZE (trg_entry) && src->depth +1 >= trg->depth) {free (delta_buf);return 0 ;
@@ -1984,7 +1988,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (trg_entry->delta_data);cache_lock ();if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; + delta_cache_size -=DELTA_SIZE (trg_entry); trg_entry->delta_data =NULL ; }if (delta_cacheable (src_size, trg_size, delta_size)) {
@@ -1997,7 +2001,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, }SET_DELTA (trg_entry, src_entry); - trg_entry->delta_size = delta_size; +SET_DELTA_SIZE (trg_entry, delta_size); trg->depth = src->depth +1 ;return 1 ;
@@ -2120,11 +2124,11 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,if (entry->delta_data && !pack_to_stdout) {unsigned long size; - size =do_compress (&entry->delta_data, entry->delta_size); + size =do_compress (&entry->delta_data,DELTA_SIZE (entry)); entry->z_delta_size = size;if (entry->z_delta_size == size) {cache_lock (); - delta_cache_size -= entry->delta_size; + delta_cache_size -=DELTA_SIZE (entry); delta_cache_size += entry->z_delta_size;cache_unlock (); }else {
pack-objects.h +22 -1
@@ -5,6 +5,7 @@#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14#define OE_Z_DELTA_BITS 16 +#define OE_DELTA_SIZE_BITS 31/* * State flags for depth-first search used for analyzing delta cycles.
@@ -80,7 +81,8 @@ struct object_entry {* uses the same base as me */ void *delta_data;/* cached delta (uncompressed) */ -unsigned long delta_size;/* delta data size (uncompressed) */ + uint32_t delta_size_:OE_DELTA_SIZE_BITS; /* delta data size (uncompressed) */ + uint32_t delta_size_valid:1; unsigned z_delta_size:OE_Z_DELTA_BITS; unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */
@@ -298,4 +300,23 @@ static inline void oe_set_size(struct object_entry *e, e->size_valid = e->size_ == size; } +static inline unsigned long oe_delta_size (struct packing_data *pack, +const struct object_entry *e) +{ +if (e->delta_size_valid) +return e->delta_size_; +return oe_size (e); +} + +static inline void oe_set_delta_size (struct packing_data *pack, +struct object_entry *e, +unsigned long size) +{ + e->delta_size_ = size; + e->delta_size_valid =e->delta_size_ == size; +if (!e->delta_size_valid && size !=oe_size (e)) +die ("BUG: this can only happen in check_object() " +"where delta size is the same as entry size" ); +} +#endif
Signature · 1 line
2.16.2.903.gd04caf5039
PATCH v4 11/11 pack-objects.h: reorder members to shrink struct object_entry
In reply to Nguyễn Thái Ngọc DuyPrevious patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in in_pack_header_size when we really run out of bits).
--- pack-objects.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
pack-objects.h +9 -8
@@ -70,35 +70,36 @@ enum dfs_state {*/ struct object_entry {struct pack_idx_entry idx; -/* object uncompressed size _if_ size_valid is true */ -uint32_t size_; -unsigned size_valid:1 ; -unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +void *delta_data;/* cached delta (uncompressed) */ off_t in_pack_offset; +uint32_t hash;/* name hint hash */ +uint32_t size_;/* object uncompressed size _if_ size_valid is true */ uint32_t delta_idx;/* delta base object */ uint32_t delta_child_idx;/* deltified objects who bases me */ uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -void *delta_data;/* cached delta (uncompressed) */ uint32_t delta_size_:OE_DELTA_SIZE_BITS ;/* delta data size (uncompressed) */ uint32_t delta_size_valid:1 ; +unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +unsigned size_valid:1 ;unsigned z_delta_size:OE_Z_DELTA_BITS ; +unsigned type_valid:1 ;unsigned type_:TYPE_BITS ;unsigned in_pack_type:TYPE_BITS ;/* could be delta */ -unsigned type_valid:1 ; -uint32_t hash;/* name hint hash */ -unsigned char in_pack_header_size;unsigned preferred_base:1 ;/* * we do not pack this, but is available * to be used as the base object to delta * objects against. */ unsigned no_try_delta:1 ; +unsigned char in_pack_header_size;unsigned tagged:1 ;/* near the very tip of refs */ unsigned filled:1 ;/* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS ;unsigned depth:OE_DEPTH_BITS ; + +/* size: 80, bit_padding: 16 bits */ };struct packing_data {
Signature · 1 line
2.16.2.903.gd04caf5039
Re: [PATCH v4 11/11] pack-objects.h: reorder members to shrink struct object_entry
Quoted 5 lines · Nguyễn Thái Ngọc Duy
Previous patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in
in_pack_header_sizewhen we really run out of bits).
Nice.
I am wondering if we need some conditional code for 32-bit platform. For example, you have uint32_t field and do things like this:
static inline int oe_size_less_than(const struct object_entry *e,
unsigned long limit)
{
if (e->size_valid)
return e->size_ < limit;
if (limit > maximum_unsigned_value_of_type(uint32_t))
return 1;
return oe_size(e) < limit;
}Do we and compilers do the right thing when your ulong is uint32_t?
Re: [PATCH v4 11/11] pack-objects.h: reorder members to shrink struct object_entry
Quoted 23 lines · Junio C Hamano
Quoted 5 lines · Nguyễn Thái Ngọc Duy
Previous patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in
in_pack_header_sizewhen we really run out of bits).Nice.
I am wondering if we need some conditional code for 32-bit platform. For example, you have
uint32_tfield and do things like this:static inline int oe_size_less_than(const struct object_entry *e, unsigned long limit) { if (e->size_valid) return e->size_ < limit; if (limit > maximum_unsigned_value_of_type(uint32_t)) return 1; return oe_size(e) < limit; }
Quoted 1 line
Do we and compilers do the right thing when your ulong is
uint32_t?
Another good point. My 32-bit build does complain
In file included from builtin/pack-objects.c:20:0:
./pack-objects.h: In function ?oe_size_less_than?:
./pack-objects.h:282:12: error: comparison is always false due to limited range of data type [-Werror=type-limits]
if (limit > maximum_unsigned_value_of_type(uint32_t))
^
Signature · 1 line
Duy
v5 cover nd/pack-objects-pack-struct updates
In reply to Nguyễn Thái Ngọc Duyv5 changes are small enough that the interdiff is pretty self explanatory (there's also a couple commit msg updates).
builtin/pack-objects.c +4 -4
@@ -1611,7 +1611,7 @@ static void drop_reused_delta(struct object_entry *entry)/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy. -* And if that fails, the error will be recorded in entry->type +* And if that fails, the error will be recorded in oe_type(entry) * and dealt with in prepare_pack(). */ oe_set_type(entry, sha1_object_info(entry->idx.oid.hash,
@@ -1968,7 +1968,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, delta_buf =create_delta (src->index, trg->data, trg_size, &delta_size, max_size);if (!delta_buf)return 0 ; -if (delta_size >=maximum_unsigned_value_of_type (uint32_t)) +if (delta_size >= (1 <<OE_DELTA_SIZE_BITS ))return 0 ;if (DELTA (trg_entry)) {
@@ -2125,8 +2125,8 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,unsigned long size; size =do_compress (&entry->delta_data,DELTA_SIZE (entry)); - entry->z_delta_size = size; -if (entry->z_delta_size == size) { +if (size < (1 <<OE_Z_DELTA_BITS )) { + entry->z_delta_size = size;cache_lock (); delta_cache_size -=DELTA_SIZE (entry); delta_cache_size += entry->z_delta_size;
pack-objects.h +21 -13
@@ -27,14 +27,15 @@ enum dfs_state {* * basic object info * ----------------- -* idx.oid is filled up before delta searching starts. idx.crc32 and -* is only valid after the object is written out and will be used for +* idx.oid is filled up before delta searching starts. idx.crc32 is +* only valid after the object is written out and will be used for * generating the index. idx.offset will be both gradually set and * used in writing phase (base objects get offset first, then deltas * refer to them) * -* "size" is the uncompressed object size. Compressed size is not -* cached (ie. raw data in a pack) but available via revindex. +* "size" is the uncompressed object size. Compressed size of the raw +* data for an object in a pack is not stored anywhere but is computed +* and made available when reverse .idx is made. * * "hash" contains a path name hash which is used for sorting the * delta list and also during delta searching. Once prepare_pack()
@@ -42,16 +43,16 @@ enum dfs_state {* * source pack info * ---------------- -* The (in_pack, in_pack_offset, in_pack_header_size) tuple contains -* the location of the object in the source pack, with or without -* header. +* The (in_pack, in_pack_offset) tuple contains the location of the +* object in the source pack. in_pack_header_size allows quickly +* skipping the header and going straight to the zlib stream. * * "type" and "in_pack_type" both describe object type. in_pack_type * may contain a delta type, while type is always the canonical type. * * deltas * ------ -* Delta links (delta, delta_child and delta_sibling) are created +* Delta links (delta, delta_child and delta_sibling) are created to * reflect that delta graph from the source pack then updated or added * during delta searching phase when we find better deltas. *
@@ -59,7 +60,7 @@ enum dfs_state {* compute_write_order(). "delta" and "delta_size" must remain valid * at object writing phase in case the delta is not cached. * -* If a delta is cached in memory and is compressed delta_data points +* If a delta is cached in memory and is compressed, delta_data points * to the data and z_delta_size contains the compressed size. If it's * uncompressed [1], z_delta_size must be zero. delta_size is always * the uncompressed size and must be valid even if the delta is not
@@ -274,12 +275,19 @@ static inline unsigned long oe_size(const struct object_entry *e) } } +static inline int contains_in_32bits (unsigned long limit) +{ +uint32_t truncated_limit = (uint32_t )limit; + +return limit == truncated_limit; +} +static inline int oe_size_less_than (const struct object_entry *e,unsigned long limit) {if (e->size_valid)return e->size_ < limit; -if (limit >maximum_unsigned_value_of_type (uint32_t)) +if (contains_in_32bits (limit))return 1 ;return oe_size (e) < limit; }
@@ -289,8 +297,8 @@ static inline int oe_size_greater_than(const struct object_entry *e, {if (e->size_valid)return e->size_ > limit; -if (limit <=maximum_unsigned_value_of_type (uint32_t)) -return 1 ; +if (contains_in_32bits (limit)) +return 0 ;return oe_size (e) > limit; }
@@ -314,7 +322,7 @@ static inline void oe_set_delta_size(struct packing_data *pack, unsigned long size) { e->delta_size_ = size; - e->delta_size_valid =e->delta_size_ == size; + e->delta_size_valid = e->delta_size_ == size;if (!e->delta_size_valid && size !=oe_size (e))die ("BUG: this can only happen in check_object() " "where delta size is the same as entry size" );
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 1/11 pack-objects: a bit of document about struct object_entry
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what.
--- pack-objects.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
pack-objects.h +45 -0
@@ -1,6 +1,51 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* + * basic object info + * ----------------- + * idx.oid is filled up before delta searching starts. idx.crc32 is + * only valid after the object is written out and will be used for + * generating the index. idx.offset will be both gradually set and + * used in writing phase (base objects get offset first, then deltas + * refer to them) + * + * "size" is the uncompressed object size. Compressed size of the raw + * data for an object in a pack is not stored anywhere but is computed + * and made available when reverse .idx is made. + * + * "hash" contains a path name hash which is used for sorting the + * delta list and also during delta searching. Once prepare_pack() + * returns it's no longer needed. + * + * source pack info + * ---------------- + * The (in_pack, in_pack_offset) tuple contains the location of the + * object in the source pack. in_pack_header_size allows quickly + * skipping the header and going straight to the zlib stream. + * + * "type" and "in_pack_type" both describe object type. in_pack_type + * may contain a delta type, while type is always the canonical type. + * + * deltas + * ------ + * Delta links (delta, delta_child and delta_sibling) are created to + * reflect that delta graph from the source pack then updated or added + * during delta searching phase when we find better deltas. + * + * delta_child and delta_sibling are last needed in + * compute_write_order(). "delta" and "delta_size" must remain valid + * at object writing phase in case the delta is not cached. + * + * If a delta is cached in memory and is compressed, delta_data points + * to the data and z_delta_size contains the compressed size. If it's + * uncompressed [1], z_delta_size must be zero. delta_size is always + * the uncompressed size and must be valid even if the delta is not + * cached. + * + * [1] during try_delta phase we don't bother with compressing because + * the delta could be quickly replaced with a better one. + */ struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 2/11 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyAn extra field type_valid is added to carry the equivalent of OBJ_BAD in the original "type" field. in_pack_type always contains a valid type so we only need 3 bits for it.
A note about accepting OBJ_NONE as "valid" type. The function read_object_list_from_stdin() can pass this value [1] and it eventually calls create_object_entry() where current code skip setting "type" field if the incoming type is zero. This does not have any bad side effects because "type" field should be memset()'d anyway.
But since we also need to set type_valid now, skipping oe_set_type() leaves type_valid zero/false, which will make oe_type() return OBJ_BAD, not OBJ_NONE anymore. Apparently we do care about OBJ_NONE in prepare_pack(). This switch from OBJ_NONE to OBJ_BAD may trigger
fatal: unable to get type of object ...
Accepting OBJ_NONE [2] does sound wrong, but this is how it is has been for a very long time and I haven't time to dig in further.
[1] See 5c49c11686 (pack-objects: better check_object() performances -
2007-04-16)
[2] 21666f1aae (convert object type handling from a string to a number
- 2007-02-26)
--- builtin/pack-objects.c | 60 ++++++++++++++++++++++++------------------ cache.h | 2 ++ object.h | 1 - pack-bitmap-write.c | 6 ++--- pack-objects.h | 20 ++++++++++++-- 5 files changed, 58 insertions(+), 31 deletions(-)
builtin/pack-objects.c +35 -25
@@ -265,7 +265,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstruct git_istream *st =NULL ;if (!usable_delta) { -if (entry->type ==OBJ_BLOB && +if (oe_type (entry) ==OBJ_BLOB && entry->size > big_file_threshold && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;
@@ -371,7 +371,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset; -enum object_type type = entry->type; +enum object_type type =oe_type (entry);off_t datalen;unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];
@@ -480,11 +480,12 @@ static off_t write_object(struct hashfile *f, to_reuse =0 ;/* explicit */ else if (!entry->in_pack) to_reuse =0 ;/* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA)/* check_object() decided it for us ... */ to_reuse = usable_delta;/* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ else if (entry->delta) to_reuse =0 ;/* we want to pack afresh */
@@ -705,8 +706,8 @@ static struct object_entry **compute_write_order(void)* And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_COMMIT && - objects[i].type !=OBJ_TAG ) +if (oe_type (&objects[i]) !=OBJ_COMMIT && +oe_type (&objects[i]) !=OBJ_TAG )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -715,7 +716,7 @@ static struct object_entry **compute_write_order(void)* And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_TREE ) +if (oe_type (&objects[i]) !=OBJ_TREE )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -1066,8 +1067,7 @@ static void create_object_entry(const struct object_id *oid, entry =packlist_alloc (&to_pack, oid->hash, index_pos); entry->hash = hash; -if (type) - entry->type = type; +oe_set_type (entry, type);if (exclude) entry->preferred_base =1 ;else
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1428,9 +1433,9 @@ static void check_object(struct object_entry *entry)switch (entry->in_pack_type) {default :/* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->in_pack_header_size = used; -if (entry->type <OBJ_COMMIT || entry->type >OBJ_BLOB ) +if (oe_type (entry) <OBJ_COMMIT ||oe_type (entry) >OBJ_BLOB ) goto give_up;unuse_pack (&w_curs);return ;
@@ -1484,7 +1489,7 @@ static void check_object(struct object_entry *entry)* deltify other objects against, in order to avoid * circular deltas. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->delta = base_entry; entry->delta_size = entry->size; entry->delta_sibling = base_entry->delta_child;
@@ -1493,7 +1498,7 @@ static void check_object(struct object_entry *entry)return ; } -if (entry->type) { +if (oe_type (entry)) {/* * This must be a delta and we already know what the * final object type is. Let's extract the actual
@@ -1516,7 +1521,7 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,16 +1576,18 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy. -* And if that fails, the error will be recorded in entry->type +* And if that fails, the error will be recorded in oe_type(entry) * and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &entry->size)); + }else { +oe_set_type (entry, type); } }
@@ -1747,10 +1755,12 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +enum object_type a_type =oe_type (a); +enum object_type b_type =oe_type (b); -if (a->type > b->type) +if (a_type > b_type)return -1 ; -if (a->type < b->type) +if (a_type < b_type)return 1 ;if (a->hash > b->hash)return -1 ;
@@ -1826,7 +1836,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,void *delta_buf;/* Don't bother doing diffs between different types */ -if (trg_entry->type != src_entry->type) +if (oe_type (trg_entry) !=oe_type (src_entry))return -1 ;/*
@@ -2432,11 +2442,11 @@ static void prepare_pack(int window, int depth)if (!entry->preferred_base) { nr_deltas++; -if (entry->type <0 ) +if (oe_type (entry) <0 )die ("unable to get type of object %s" ,oid_to_hex (&entry->idx.oid)); }else { -if (entry->type <0 ) { +if (oe_type (entry) <0 ) {/* * This object is not found, but we * don't have to include it anyway.
@@ -2545,7 +2555,7 @@ static void read_object_list_from_stdin(void)die ("expected object ID, got garbage:\n %s" , line);add_preferred_base_object (p +1 ); -add_object_entry (&oid,0 , p +1 ,0 ); +add_object_entry (&oid,OBJ_NONE , p +1 ,0 ); } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-bitmap-write.c +3 -3
@@ -64,12 +64,12 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, entry->in_pack_pos = i; -switch (entry->type) { +switch (oe_type (entry)) {case OBJ_COMMIT :case OBJ_TREE :case OBJ_BLOB :case OBJ_TAG : - real_type = entry->type; + real_type =oe_type (entry);break ;default :
@@ -98,7 +98,7 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,default :die ("Missing type information for %s (%d/%d)" ,oid_to_hex (&entry->idx.oid), real_type, - entry->type); +oe_type (entry)); } } }
pack-objects.h +18 -2
@@ -59,8 +59,9 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ + unsigned type_:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ + unsigned type_valid:1;uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size;
@@ -123,4 +124,19 @@ static inline uint32_t pack_name_hash(const char *name) return hash; } +static inline enum object_type oe_type (const struct object_entry *e) +{ +return e->type_valid ? e->type_ :OBJ_BAD ; +} + +static inline void oe_set_type (struct object_entry *e, +enum object_type type) +{ +if (type >=OBJ_ANY ) +die ("BUG: OBJ_ANY cannot be set in pack-objects code" ); + + e->type_valid = type >=OBJ_NONE ; + e->type_ = (unsigned )type; +} +#endif
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 3/11 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +17 -11
@@ -1,6 +1,21 @@#ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* +* State flags for depth-first search used for analyzing delta cycles. +* +* The depth is measured in delta-links to the base (so if A is a delta +* against B, then A has a depth of 1, and B a depth of 0). +*/ +enum dfs_state { +DFS_NONE =0 , +DFS_ACTIVE , +DFS_DONE , +DFS_NUM_STATES +}; +/* * basic object info * -----------------
@@ -73,19 +88,10 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; + };struct packing_data {
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 4/11 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyBecause of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 4 +++- Documentation/git-repack.txt | 4 +++- builtin/pack-objects.c | 4 ++++ pack-objects.h | 5 ++--- 5 files changed, 13 insertions(+), 5 deletions(-)
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +3 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth >= (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +2 -3
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -89,9 +90,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; - -int depth; - + unsigned depth:OE_DEPTH_BITS; };struct packing_data {
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
Re: [PATCH v5 04/11] pack-objects: use bitfield for object_entry::depth
On Sat, Mar 17 2018, Nguyễn Thái Ngọc Duy jotted:
builtin/pack-objects.c int cmd_pack_objects(int argc, const char **argv, const char *prefix) · line 3074
Quoted 7 lines
Because of struct packing from now on we can only handle max depth 4095
[...]builtin/pack-objects.c +4
-0+if (depth >= (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS )); +
This has a off-by-one error:
$ git repack --depth=4096
fatal: delta chain depth 4096 is greater than maximum limit 4096Per the check we should be feeding (1 << OE_DEPTH_BITS) - 1 to die().
PATCH v5 5/11 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not).
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 7 deletions(-)
builtin/pack-objects.c +2 -1
@@ -879,7 +879,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +oe_set_in_pack_pos (to_pack, entry, i);switch (oe_type (entry)) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return oe_in_pack_pos (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =oe_in_pack_pos (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +15 -1
@@ -79,7 +79,6 @@ struct object_entry { unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; unsigned preferred_base:1; /** we do not pack this, but is available
@@ -99,6 +98,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
@@ -144,4 +145,17 @@ static inline void oe_set_type(struct object_entry *e, e->type_ = (unsigned)type; } +static inline unsigned int oe_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e) +{ +return pack->in_pack_pos[e - pack->objects]; +} + +static inline void oe_set_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e, +unsigned int pos) +{ + pack->in_pack_pos[e - pack->objects] = pos; +} +#endif
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 6/11 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index instead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
--- Documentation/git-pack-objects.txt | 9 ++++++ builtin/pack-objects.c | 40 +++++++++++++++++---------- cache.h | 1 + pack-objects.h | 44 +++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 15 deletions(-)
Documentation/git-pack-objects.txt +9 -0
@@ -269,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
builtin/pack-objects.c +26 -14
@@ -29,6 +29,8 @@#include "list.h" #include "packfile.h" +#define IN_PACK (obj) oe_in_pack(&to_pack, obj) +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -367,7 +369,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p = entry->in_pack; +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -478,7 +480,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!entry->in_pack) +else if (!IN_PACK (entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (oe_type (entry) ==OBJ_REF_DELTA ||oe_type (entry) ==OBJ_OFS_DELTA )
@@ -1025,7 +1027,7 @@ static int want_object_in_pack(const struct object_id *oid,if (*found_pack) { want =want_found_object (exclude, *found_pack);if (want !=-1 ) -return want; + goto done; }list_for_each (pos, &packed_git_mru) {
@@ -1048,11 +1050,16 @@ static int want_object_in_pack(const struct object_id *oid,if (!exclude && want >0 )list_move (&p->mru, &packed_git_mru);if (want !=-1 ) -return want; + goto done; } } -return 1 ; + want =1 ; +done: +if (want && *found_pack && !(*found_pack)->index) +oe_add_pack (&to_pack, *found_pack); + +return want; } static void create_object_entry(const struct object_id *oid,
@@ -1074,7 +1081,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { - entry->in_pack = found_pack; +oe_set_in_pack (entry, found_pack); entry->in_pack_offset = found_offset; }
@@ -1399,8 +1406,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (entry->in_pack) { -struct packed_git *p = entry->in_pack; +if (IN_PACK (entry)) { +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1535,14 +1542,16 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +const struct packed_git *a_in_pack =IN_PACK (a); +const struct packed_git *b_in_pack =IN_PACK (b);/* avoid filesystem trashing with loose objects */ -if (!a->in_pack && !b->in_pack) +if (!a_in_pack && !b_in_pack)return oidcmp (&a->idx.oid, &b->idx.oid); -if (a->in_pack < b->in_pack) +if (a_in_pack < b_in_pack)return -1 ; -if (a->in_pack > b->in_pack) +if (a_in_pack > b_in_pack)return 1 ;return a->in_pack_offset < b->in_pack_offset ?-1 : (a->in_pack_offset > b->in_pack_offset);
@@ -1578,7 +1587,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1848,8 +1857,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && +if (reuse_delta &&IN_PACK (trg_entry) && +IN_PACK (trg_entry) ==IN_PACK (src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -3191,6 +3200,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } +/* make sure IN_PACK(0) return NULL */ +oe_add_pack (&to_pack,NULL ); +if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );if (!use_internal_rev_list)
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
pack-objects.h +43 -1
72 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 7/11 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyThese delta pointers always point to elements in the objects[] array in packing_data struct. We can only hold maximum 4G of those objects because the array size in nr_objects is uint32_t. We could use uint32_t indexes to address these elements instead of pointers. On 64-bit architecture (8 bytes per pointer) this would save 4 bytes per pointer.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 116 ++++++++++++++++++++++------------------- pack-objects.h | 67 ++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 59 deletions(-)
builtin/pack-objects.c +62 -54
@@ -30,6 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA (obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),
@@ -127,11 +133,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -288,12 +294,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -317,7 +323,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -343,7 +349,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -379,8 +385,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -408,7 +414,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -427,7 +433,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -467,13 +473,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -489,7 +495,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -541,12 +547,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +SET_DELTA (e,NULL );break ;default :break ;
@@ -608,34 +614,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -646,7 +652,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -661,8 +667,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +SET_DELTA_CHILD (&objects[i],NULL ); +SET_DELTA_SIBLING (&objects[i],NULL ); }/*
@@ -672,11 +678,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1498,10 +1504,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ oe_set_type (entry, entry->in_pack_type); - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1572,17 +1578,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +SET_DELTA (entry,NULL ); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1622,7 +1630,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1647,7 +1655,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1670,7 +1678,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1685,7 +1693,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1870,7 +1878,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1946,7 +1954,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1975,7 +1983,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1984,13 +1992,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2059,7 +2067,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2109,7 +2117,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2117,7 +2125,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2438,7 +2446,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +62 -5
77 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 8/11 pack-objects: shrink z_delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyWe only cache deltas when it's smaller than a certain limit. This limit defaults to 1000 but save its compressed length in a 64-bit field. Shrink that field down to 16 bits, so you can only cache 65kb deltas. Larger deltas must be recomputed at when the pack is written down.
--- Documentation/config.txt | 3 ++- builtin/pack-objects.c | 22 ++++++++++++++++------ pack-objects.h | 3 ++- 3 files changed, 20 insertions(+), 8 deletions(-)
Documentation/config.txt +2 -1
@@ -2449,7 +2449,8 @@ pack.deltaCacheLimit:: The maximum size of a delta, that is cached in linkgit:git-pack-objects[1]. This cache is used to speed up the writing object phase by not having to recompute the final delta - result once the best match for all objects is found. Defaults to 1000. + result once the best match for all objects is found. + Defaults to 1000. Maximum value is 65535. pack.threads:: Specifies the number of threads to spawn when searching for best
builtin/pack-objects.c +16 -6
@@ -2105,12 +2105,19 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* between writes at that moment. */ if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size =do_compress (&entry->delta_data, - entry->delta_size); -cache_lock (); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; -cache_unlock (); +unsigned long size; + + size =do_compress (&entry->delta_data, entry->delta_size); +if (size < (1 <<OE_Z_DELTA_BITS )) { + entry->z_delta_size = size; +cache_lock (); + delta_cache_size -= entry->delta_size; + delta_cache_size += entry->z_delta_size; +cache_unlock (); + }else { +FREE_AND_NULL (entry->delta_data); + entry->z_delta_size =0 ; + } }/* if we made n a delta, and if n is already at max
@@ -3089,6 +3096,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (depth >= (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), depth, (1 <<OE_DEPTH_BITS )); +if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS )) +die (_ ("pack.deltaCacheLimit is greater than maximum limit %d" ), +1 <<OE_Z_DELTA_BITS );argv_array_push (&rp,"pack-objects" );if (thin) {
pack-objects.h +2 -1
@@ -4,6 +4,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14 +#define OE_Z_DELTA_BITS 16/* * State flags for depth-first search used for analyzing delta cycles.
@@ -79,7 +80,7 @@ struct object_entry {*/ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ -unsigned long z_delta_size;/* delta data size (compressed) */ + unsigned z_delta_size:OE_Z_DELTA_BITS; unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 9/11 pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyIt's very very rare that an uncompressd object is larger than 4GB (partly because Git does not handle those large files very well to begin with). Let's optimize it for the common case where object size is smaller than this limit.
Shrink size field down to 32 bits [1] and one overflow bit. If the size is too large, we read it back from disk.
Add two compare helpers that can take advantage of the overflow bit (e.g. if the file is 4GB+, chances are it's already larger than core.bigFileThreshold and there's no point in comparing the actual value).
A small note about the conditional oe_set_size() in check_object(). Technically if we don't get a valid type, it's not wrong if we set uninitialized value "size" (we don't pre-initialize this and sha1_object_info will not assign anything when it fails to get the info).
This how changes the writing code path slightly which emits different error messages (either way we die). One of our tests in t5530 depends on this specific error message. Let's just keep the test as-is and play safe by not assigning random value. That might trigger valgrind anyway.
[1] it's actually already 32 bits on Windows
--- builtin/pack-objects.c | 49 ++++++++++++++++++++++++++--------------- pack-objects.h | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 19 deletions(-)
builtin/pack-objects.c +31 -18
@@ -274,7 +274,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entif (!usable_delta) {if (oe_type (entry) ==OBJ_BLOB && - entry->size > big_file_threshold && +oe_size_greater_than (entry, big_file_threshold) && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;else {
@@ -384,12 +384,13 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; +unsigned long entry_size =oe_size (entry);if (DELTA (entry)) type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), - type, entry->size); + type, entry_size); offset = entry->in_pack_offset; revidx =find_pack_revindex (p, offset);
@@ -406,7 +407,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size;if (!pack_to_stdout && p->index_version ==1 && -check_pack_inflate (p, &w_curs, offset, datalen, entry->size)) { +check_pack_inflate (p, &w_curs, offset, datalen, entry_size)) {error ("corrupt packed object for %s" ,oid_to_hex (&entry->idx.oid));unuse_pack (&w_curs);
@@ -1412,6 +1413,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { +unsigned long size; +if (IN_PACK (entry)) {struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;
@@ -1431,13 +1434,14 @@ static void check_object(struct object_entry *entry)*/ used =unpack_object_header_buffer (buf, avail, &type, - &entry->size); + &size);if (used ==0 ) goto give_up;if (type <0 )die ("BUG: invalid type %d" , type); entry->in_pack_type = type; +oe_set_size (entry, size);/* * Determine if this is a delta and if so whether we can
@@ -1505,7 +1509,7 @@ static void check_object(struct object_entry *entry)*/ oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry); - entry->delta_size = entry->size; + entry->delta_size =oe_size (entry); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1513,14 +1517,17 @@ static void check_object(struct object_entry *entry) }if (oe_type (entry)) { +unsigned long size; + + size =get_size_from_delta (p, &w_curs, + entry->in_pack_offset + entry->in_pack_header_size);/* * This must be a delta and we already know what the * final object type is. Let's extract the actual * object size from the delta header. */ - entry->size =get_size_from_delta (p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); -if (entry->size ==0 ) +oe_set_size (entry, size); +if (oe_size_less_than (entry,1 )) goto give_up;unuse_pack (&w_curs);return ;
@@ -1535,13 +1542,15 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } -oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size)); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored * as a preferred base. Doing so can result in a larger * pack file, but the transfer will still take place. */ +if (entry->type_valid) +oe_set_size (entry, size); }static int pack_offset_sort (const void *_a,const void *_b)
@@ -1581,6 +1590,7 @@ static void drop_reused_delta(struct object_entry *entry)unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; +unsigned long size;while (*idx) {struct object_entry *oe = &to_pack.objects[*idx -1 ];
@@ -1593,7 +1603,7 @@ static void drop_reused_delta(struct object_entry *entry)SET_DELTA (entry,NULL ); entry->depth =0 ; - oi.sizep = &entry->size; + oi.sizep = &size; oi.typep = &type;if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/*
@@ -1603,10 +1613,11 @@ static void drop_reused_delta(struct object_entry *entry)* and dealt with in prepare_pack(). */ oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, - &entry->size)); + &size)); }else {oe_set_type (entry, type); } +oe_set_size (entry, size); }/*
@@ -1746,7 +1757,7 @@ static void get_object_details(void)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = sorted_by_offset[i];check_object (entry); -if (big_file_threshold < entry->size) +if (oe_size_greater_than (entry, big_file_threshold)) entry->no_try_delta =1 ; }
@@ -1775,6 +1786,8 @@ static int type_size_sort(const void *_a, const void *_b)const struct object_entry *b = *(struct object_entry **)_b;enum object_type a_type =oe_type (a);enum object_type b_type =oe_type (b); +unsigned long a_size =oe_size (a); +unsigned long b_size =oe_size (b);if (a_type > b_type)return -1 ;
@@ -1788,9 +1801,9 @@ static int type_size_sort(const void *_a, const void *_b)return -1 ;if (a->preferred_base < b->preferred_base)return 1 ; -if (a->size > b->size) +if (a_size > b_size)return -1 ; -if (a->size < b->size) +if (a_size < b_size)return 1 ;return a < b ?-1 : (a > b);/* newest first */ }
@@ -1877,7 +1890,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,return 0 ;/* Now some size filtering heuristics. */ - trg_size = trg_entry->size; + trg_size =oe_size (trg_entry);if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ;
@@ -1889,7 +1902,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, (max_depth - ref_depth +1 );if (max_size ==0 )return 0 ; - src_size = src_entry->size; + src_size =oe_size (src_entry); sizediff = src_size < trg_size ? trg_size - src_size :0 ;if (sizediff >= max_size)return 0 ;
@@ -2009,7 +2022,7 @@ static unsigned long free_unpacked(struct unpacked *n)free_delta_index (n->index); n->index =NULL ;if (n->data) { - freed_mem += n->entry->size; + freed_mem +=oe_size (n->entry);FREE_AND_NULL (n->data); } n->entry =NULL ;
@@ -2459,7 +2472,7 @@ static void prepare_pack(int window, int depth)*/ continue ; -if (entry->size <50 ) +if (oe_size_less_than (entry,50 ))continue ;if (entry->no_try_delta)
pack-objects.h +49 -1
60 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
Re: [PATCH v5 09/11] pack-objects: shrink size field in struct object_entry
On Sat, Mar 17 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 1 line
It's very very rare that an uncompressd object is larger than 4GB
s/uncompressd/uncompressed/
Re: [PATCH v5 09/11] pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyReview
Quoted 2 lines · Nguyễn Thái Ngọc Duy
(quoted diff) +2
-0+static inline int contains_in_32bits(unsigned long limit) +{
This name somehow does not sound right.
If the verb "contain" must be used, the way to phrase what this function does with the verb is to say "limit can be contained in a 32-bit int", so "contains" is probably where the funniness comes from.
"fits in 32bits" is OK, I think.
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 280
Quoted 4 lines
pack-objects.h +4
-0+uint32_t truncated_limit = (uint32_t )limit; + +return limit == truncated_limit; +}
I am guessing that a compiler that is clever enough will make this function a no-op on a 32-bit arch and that is why it is a static inline function?
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 286
Quoted 5 lines
pack-objects.h +5
-0+static inline int oe_size_less_than (const struct object_entry *e, +unsigned long limit) +{ +if (e->size_valid) +return e->size_ < limit;
e->size_ is the true size so we can compare it to see if it is smaller than limit.
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 288
Quoted 2 lines
pack-objects.h +2
-0+if (contains_in_32bits (limit)) +return 1 ;
If limit is small enough, and because e->size_valid means e->size_ does not fit in 32-bit, we know size is larger than limit. Shouldn't we be returning 0 that means "no, the size is not less than limit" from here?
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 296
Quoted 8 lines
pack-objects.h +8
-0+return oe_size (e) < limit; +} + +static inline int oe_size_greater_than (const struct object_entry *e, +unsigned long limit) +{ +if (e->size_valid) +return e->size_ > limit;
e->size_ is the true size so we compare and return if it is larger than limit.
Review
Quoted 2 lines
(quoted diff) +2
-0+ if (contains_in_32bits(limit)) + return 0;
Now e->size_ is larger than what would fit within 32-bit. If limit fits within 32-bit, then size must be larger than limit. Again, shouldn't we be returning 1 that means "yes, the size is greater than limit" from here?
Quoted 11 lines
pack-objects.h +10
-0+ return oe_size(e) > limit; +} + +static inline void oe_set_size (struct object_entry *e, +unsigned long size) +{ + e->size_ = size; + e->size_valid = e->size_ == size; +} +#endif
Re: [PATCH v5 09/11] pack-objects: shrink size field in struct object_entry
Quoted 8 lines · Junio C Hamano
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 280
Quoted 4 lines
pack-objects.h +4
-0+uint32_t truncated_limit = (uint32_t )limit; + +return limit == truncated_limit; +}I am guessing that a compiler that is clever enough will make this function a no-op on a 32-bit arch and that is why it is a static inline function?
It's a separate function because I don't want to duplicate this == logic twice. Even if the compiler does not optimize this, it's still much cheaper than oe_sze() which involves disk access.
Quoted 9 lines
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 286
Quoted 5 lines
pack-objects.h +5
-0+static inline int oe_size_less_than (const struct object_entry *e, +unsigned long limit) +{ +if (e->size_valid) +return e->size_ < limit;
e->size_is the true size so we can compare it to see if it is smaller than limit.
Quoted 7 lines
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 288
Quoted 2 lines
pack-objects.h +2
-0+if (contains_in_32bits (limit)) +return 1 ;If limit is small enough, and because
e->size_validmeanse->size_does not fit in 32-bit, we know size is larger than limit. Shouldn't we be returning 0 that means "no, the size is not less than limit" from here?
Argh!!! This logic keeps messing with my brain.
Signature · 1 line
Duy
PATCH v5 10/11 pack-objects: shrink delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyAllowing a delta size of 64 bits is crazy. Shrink this field down to 31 bits with one overflow bit.
If we find an existing delta larger than 2GB, we do not cache delta_size at all and will get the value from oe_size(), potentially from disk if it's larger than 4GB.
--- builtin/pack-objects.c | 24 ++++++++++++++---------- pack-objects.h | 23 ++++++++++++++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-)
builtin/pack-objects.c +14 -10
@@ -30,10 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA_SIZE (obj) oe_delta_size(&to_pack, obj)#define DELTA (obj) oe_delta(&to_pack, obj)#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj)#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj)#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_SIZE (obj, val) oe_set_delta_size(&to_pack, obj, val)#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val)#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)
@@ -140,7 +142,7 @@ static void *get_delta(struct object_entry *entry) oid_to_hex(&DELTA(entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 ); -if (!delta_buf || delta_size != entry->delta_size) +if (!delta_buf || delta_size !=DELTA_SIZE (entry))die ("delta size changed" );free (buf);free (base_buf);
@@ -291,14 +293,14 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entFREE_AND_NULL (entry->delta_data); entry->z_delta_size =0 ; }else if (entry->delta_data) { - size = entry->delta_size; + size =DELTA_SIZE (entry); buf = entry->delta_data; entry->delta_data =NULL ; type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); - size = entry->delta_size; + size =DELTA_SIZE (entry); type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -1509,7 +1511,7 @@ static void check_object(struct object_entry *entry)*/ oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry); - entry->delta_size =oe_size (entry); +SET_DELTA_SIZE (entry,oe_size (entry)); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1895,7 +1897,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, max_size = trg_size/2 -20 ; ref_depth =1 ; }else { - max_size = trg_entry->delta_size; + max_size =DELTA_SIZE (trg_entry); ref_depth = trg->depth; } max_size = (uint64_t)max_size * (max_depth - src->depth) /
@@ -1966,10 +1968,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, delta_buf =create_delta (src->index, trg->data, trg_size, &delta_size, max_size);if (!delta_buf)return 0 ; +if (delta_size >= (1 <<OE_DELTA_SIZE_BITS )) +return 0 ;if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ -if (delta_size == trg_entry->delta_size && +if (delta_size ==DELTA_SIZE (trg_entry) && src->depth +1 >= trg->depth) {free (delta_buf);return 0 ;
@@ -1984,7 +1988,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (trg_entry->delta_data);cache_lock ();if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; + delta_cache_size -=DELTA_SIZE (trg_entry); trg_entry->delta_data =NULL ; }if (delta_cacheable (src_size, trg_size, delta_size)) {
@@ -1997,7 +2001,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, }SET_DELTA (trg_entry, src_entry); - trg_entry->delta_size = delta_size; +SET_DELTA_SIZE (trg_entry, delta_size); trg->depth = src->depth +1 ;return 1 ;
@@ -2120,11 +2124,11 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,if (entry->delta_data && !pack_to_stdout) {unsigned long size; - size =do_compress (&entry->delta_data, entry->delta_size); + size =do_compress (&entry->delta_data,DELTA_SIZE (entry));if (size < (1 <<OE_Z_DELTA_BITS )) { entry->z_delta_size = size;cache_lock (); - delta_cache_size -= entry->delta_size; + delta_cache_size -=DELTA_SIZE (entry); delta_cache_size += entry->z_delta_size;cache_unlock (); }else {
pack-objects.h +22 -1
@@ -5,6 +5,7 @@#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14#define OE_Z_DELTA_BITS 16 +#define OE_DELTA_SIZE_BITS 31/* * State flags for depth-first search used for analyzing delta cycles.
@@ -81,7 +82,8 @@ struct object_entry {* uses the same base as me */ void *delta_data;/* cached delta (uncompressed) */ -unsigned long delta_size;/* delta data size (uncompressed) */ + uint32_t delta_size_:OE_DELTA_SIZE_BITS; /* delta data size (uncompressed) */ + uint32_t delta_size_valid:1; unsigned z_delta_size:OE_Z_DELTA_BITS; unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */
@@ -306,4 +308,23 @@ static inline void oe_set_size(struct object_entry *e, e->size_valid = e->size_ == size; } +static inline unsigned long oe_delta_size (struct packing_data *pack, +const struct object_entry *e) +{ +if (e->delta_size_valid) +return e->delta_size_; +return oe_size (e); +} + +static inline void oe_set_delta_size (struct packing_data *pack, +struct object_entry *e, +unsigned long size) +{ + e->delta_size_ = size; + e->delta_size_valid = e->delta_size_ == size; +if (!e->delta_size_valid && size !=oe_size (e)) +die ("BUG: this can only happen in check_object() " +"where delta size is the same as entry size" ); +} +#endif
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v5 11/11 pack-objects.h: reorder members to shrink struct object_entry
In reply to Nguyễn Thái Ngọc DuyPrevious patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in in_pack_header_size when we really run out of bits).
--- pack-objects.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
pack-objects.h +9 -8
@@ -71,35 +71,36 @@ enum dfs_state {*/ struct object_entry {struct pack_idx_entry idx; -/* object uncompressed size _if_ size_valid is true */ -uint32_t size_; -unsigned size_valid:1 ; -unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +void *delta_data;/* cached delta (uncompressed) */ off_t in_pack_offset; +uint32_t hash;/* name hint hash */ +uint32_t size_;/* object uncompressed size _if_ size_valid is true */ uint32_t delta_idx;/* delta base object */ uint32_t delta_child_idx;/* deltified objects who bases me */ uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -void *delta_data;/* cached delta (uncompressed) */ uint32_t delta_size_:OE_DELTA_SIZE_BITS ;/* delta data size (uncompressed) */ uint32_t delta_size_valid:1 ; +unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +unsigned size_valid:1 ;unsigned z_delta_size:OE_Z_DELTA_BITS ; +unsigned type_valid:1 ;unsigned type_:TYPE_BITS ;unsigned in_pack_type:TYPE_BITS ;/* could be delta */ -unsigned type_valid:1 ; -uint32_t hash;/* name hint hash */ -unsigned char in_pack_header_size;unsigned preferred_base:1 ;/* * we do not pack this, but is available * to be used as the base object to delta * objects against. */ unsigned no_try_delta:1 ; +unsigned char in_pack_header_size;unsigned tagged:1 ;/* near the very tip of refs */ unsigned filled:1 ;/* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS ;unsigned depth:OE_DEPTH_BITS ; + +/* size: 80, bit_padding: 16 bits */ };struct packing_data {
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
Re: [PATCH v5 11/11] pack-objects.h: reorder members to shrink struct object_entry
On Sat, Mar 17 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 5 lines
Previous patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in
in_pack_header_sizewhen we really run out of bits).
Given what I mentioned in [email protected] just now I think we should add this to the commit message.
This is the last in a series of memory reduction patches (see "pack-objects: a bit of document about struct object_entry" for the first one).
Overall they've reduced repack memory size on linux.git from 3.747G to 3.424G, or by around 320M, a decrease of 8.5%. The runtime of repack has stayed the same throughout this series. Ævar's testing on a big monorepo he has access to (bigger than linux.git) has shown a 7.9% reduction, so the overall expected improvement should be somewhere around 8%.
See [email protected] on-list
(https://public-inbox.org/git/[email protected]/)
for more detailed numbers and a test script used to produce the numbers cited above.
Thanks again for working on this.
Quoted 54 lines
Signed-off-by Nguyễn Thái Ngọc Duy <[email protected]>--- pack-objects.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)pack-objects.h +9
-8@@ -71,35 +71,36 @@ enum dfs_state {*/ struct object_entry {struct pack_idx_entry idx; -/* object uncompressed size _if_ size_valid is true */ -uint32_t size_; -unsigned size_valid:1 ; -unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +void *delta_data;/* cached delta (uncompressed) */ off_t in_pack_offset; +uint32_t hash;/* name hint hash */ +uint32_t size_;/* object uncompressed size _if_ size_valid is true */ uint32_t delta_idx;/* delta base object */ uint32_t delta_child_idx;/* deltified objects who bases me */ uint32_t delta_sibling_idx;/* other deltified objects who * uses the same base as me */ -void *delta_data;/* cached delta (uncompressed) */ uint32_t delta_size_:OE_DELTA_SIZE_BITS ;/* delta data size (uncompressed) */ uint32_t delta_size_valid:1 ; +unsigned in_pack_idx:OE_IN_PACK_BITS ;/* already in pack */ +unsigned size_valid:1 ;unsigned z_delta_size:OE_Z_DELTA_BITS ; +unsigned type_valid:1 ;unsigned type_:TYPE_BITS ;unsigned in_pack_type:TYPE_BITS ;/* could be delta */ -unsigned type_valid:1 ; -uint32_t hash;/* name hint hash */ -unsigned char in_pack_header_size;unsigned preferred_base:1 ;/* * we do not pack this, but is available * to be used as the base object to delta * objects against. */ unsigned no_try_delta:1 ; +unsigned char in_pack_header_size;unsigned tagged:1 ;/* near the very tip of refs */ unsigned filled:1 ;/* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS ;unsigned depth:OE_DEPTH_BITS ; + +/* size: 80, bit_padding: 16 bits */ };struct packing_data {
Re: [PATCH v5 11/11] pack-objects.h: reorder members to shrink struct object_entry
Quoted 23 lines · Ævar Arnfjörð Bjarmason
On Sat, Mar 17 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 5 lines
Previous patches leave lots of holes and padding in this struct. This patch reorders the members and shrinks the struct down to 80 bytes (from 136 bytes, before any field shrinking is done) with 16 bits to spare (and a couple more in
in_pack_header_sizewhen we really run out of bits).Given what I mentioned in [email protected] just now I think we should add this to the commit message.
This is the last in a series of memory reduction patches (see "pack-objects: a bit of document about struct
object_entry" for the first one).Overall they've reduced repack memory size on linux.git from 3.747G to 3.424G, or by around 320M, a decrease of 8.5%. The runtime of repack has stayed the same throughout this series. Ævar's testing on a big monorepo he has access to (bigger than linux.git) has shown a 7.9% reduction, so the overall expected improvement should be somewhere around 8%.
Quoted 4 lines
See [email protected] on-list
(https://public-inbox.org/git/[email protected]/)
for more detailed numbers and a test script used to produce the numbers cited above.
Yeah.
I probably should add something that was on my mind but never written out. These shrinking and packing definitely slow down access to these struct members (more instructions to read or write). However, since pack-objects is mostly IO-bound, and when it's CPU-bound, I think the hot path is either inflating objects or generating deltas, these slowdowns do not matter (and smaller cache footprint helps too)
Signature · 1 line
Duy
Re: [PATCH v5 00/11] nd/pack-objects-pack-struct updates
In reply to Nguyễn Thái Ngọc DuyOn Sat, Mar 17 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 2 lines
v5 changes are small enough that the interdiff is pretty self explanatory (there's also a couple commit msg updates).
I've been testing this and it's definitely an improvement. I think it would be good to get some mention in the commit messages themselves of the incremental improvement, to that end I wrote this:
$ cat /tmp/howmuch-mem.sh
#!/bin/sh
cd /tmp &&
(
for i in {1..3}
do
/usr/bin/time -f MaxRSS:%M ~/g/git/git --git-dir=/tmp/linux.git --exec-path=/home/avar/g/git repack -A -d 2>&1
done | grep MaxRSS: | sort -n | head -n 1 | tr '\n' '\t' &&
git git-commit-summary &&
echo
) | tee -a /tmp/git-memory.logI.e. we repack linux.git (I'd already repacked it once) and do three runs, and take the lowest RSS size. This yields (I rebased your series on top of [email protected]:git/git.git master and pushed it to [email protected]:avar/git.git pack-objects-reduce-memory-footprint), via:
git rebase --exec='make -j8 CFLAGS="-O3" && /tmp/howmuch-mem.sh' -i
That gave me, kb on the first column:
MaxRSS:3746648 f23a196dd9 ("pack-objects: a bit of document about struct object_entry", 2018-03-01)
MaxRSS:3700696 953b6473d7 ("pack-objects: turn type and in_pack_type to bitfields", 2018-03-01)
MaxRSS:3700404 6cbe573539 ("pack-objects: use bitfield for object_entry::dfs_state", 2018-03-01)
MaxRSS:3654044 0b93ebcae9 ("pack-objects: use bitfield for object_entry::depth", 2018-03-01)
MaxRSS:3654040 67a4d48773 ("pack-objects: move in_pack_pos out of struct object_entry", 2018-03-01) [X]
MaxRSS:3654104 e77319c65a ("pack-objects: move in_pack out of struct object_entry", 2018-03-01) [X]
MaxRSS:3608096 a72cfcfea3 ("pack-objects: refer to delta objects by index instead of pointer", 2018-03-01)
MaxRSS:3562212 76eaa779eb ("pack-objects: shrink z_delta_size field in struct object_entry", 2018-03-05)
MaxRSS:3515164 42e28dd4b3 ("pack-objects: shrink size field in struct object_entry", 2018-03-05)
MaxRSS:3469440 26eba3ded4 ("pack-objects: shrink delta_size field in struct object_entry", 2018-03-05)
MaxRSS:3423704 c6493de964 ("pack-objects.h: reorder members to shrink struct object_entry", 2018-03-12)I.e. on git.git we end up with just over a a 8.5% reduction, and interestingly have a slight increase over a past commit in one change, and one that just makes 4kb of difference (marked via [X] above).
Also, your v0 says it overall saves 260MB of memory. According to this it's 320MB. You did note some reductions in subsequent patches, but it's worth calling that out explicitly.
I have a bigger in-house repo that looks like this with this change:
MaxRSS:4753120 f23a196dd9 ("pack-objects: a bit of document about struct object_entry", 2018-03-01)
MaxRSS:4699084 953b6473d7 ("pack-objects: turn type and in_pack_type to bitfields", 2018-03-01)
MaxRSS:4699028 6cbe573539 ("pack-objects: use bitfield for object_entry::dfs_state", 2018-03-01)
MaxRSS:4645452 0b93ebcae9 ("pack-objects: use bitfield for object_entry::depth", 2018-03-01)
MaxRSS:4645288 67a4d48773 ("pack-objects: move in_pack_pos out of struct object_entry", 2018-03-01)
MaxRSS:4645548 e77319c65a ("pack-objects: move in_pack out of struct object_entry", 2018-03-01)
MaxRSS:4591484 a72cfcfea3 ("pack-objects: refer to delta objects by index instead of pointer", 2018-03-01)
MaxRSS:4537980 76eaa779eb ("pack-objects: shrink z_delta_size field in struct object_entry", 2018-03-05)
MaxRSS:4484148 42e28dd4b3 ("pack-objects: shrink size field in struct object_entry", 2018-03-05)
MaxRSS:4430404 26eba3ded4 ("pack-objects: shrink delta_size field in struct object_entry", 2018-03-05)
MaxRSS:4376148 c6493de964 ("pack-objects.h: reorder members to shrink struct object_entry", 2018-03-12)I.e. a tad more than a 7.9% reduction in memory use.
This series also doesn't make a difference to the total runtime (which is good, just wanted to make sure). On linux.git on my box best out of three is 1:15.74 before and 1:14.93 after, which is within the margin of random error.
Re: [PATCH v5 00/11] nd/pack-objects-pack-struct updates
On Sat, Mar 17 2018, Ævar Arnfjörð Bjarmason jotted:
Quoted 1 line
[...]I.e. on git.git we end up with just over a a 8.5% reduction, and[...]
Urgh, sorry, this should say "on linux.git...". None of these numbers came from testing git.git.
v6 cover nd/pack-objects-pack-struct updates
In reply to Nguyễn Thái Ngọc Duyv6 fixes the one optimization that I just couldn't get right, fixes two off-by-one error messages and a couple commit message update (biggest change is in 11/11 to record some numbers from AEvar)
builtin/pack-objects.c +2 -2
@@ -3112,10 +3112,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (depth >= (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), - depth, (1 <<OE_DEPTH_BITS )); + depth, (1 <<OE_DEPTH_BITS ) -1 );if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS ))die (_ ("pack.deltaCacheLimit is greater than maximum limit %d" ), -1 <<OE_Z_DELTA_BITS ); + (1 <<OE_Z_DELTA_BITS ) -1 );argv_array_push (&rp,"pack-objects" );if (thin) {
pack-objects.h +13 -5
@@ -275,7 +275,7 @@ static inline unsigned long oe_size(const struct object_entry *e) } } -static inline int contains_in_32bits (unsigned long limit) +static inline int oe_fits_in_32bits (unsigned long limit) {uint32_t truncated_limit = (uint32_t )limit;
@@ -287,8 +287,8 @@ static inline int oe_size_less_than(const struct object_entry *e, {if (e->size_valid)return e->size_ < limit; -if (contains_in_32bits (limit)) -return 1 ; +if (oe_fits_in_32bits (limit))/* limit < 2^32 <= size ? */ +return 0 ;return oe_size (e) < limit; }
@@ -297,8 +297,8 @@ static inline int oe_size_greater_than(const struct object_entry *e, {if (e->size_valid)return e->size_ > limit; -if (contains_in_32bits (limit)) -return 0 ; +if (oe_fits_in_32bits (limit))/* limit < 2^32 <= size ? */ +return 1 ;return oe_size (e) > limit; }
@@ -307,6 +307,14 @@ static inline void oe_set_size(struct object_entry *e, { e->size_ = size; e->size_valid = e->size_ == size; + +if (!e->size_valid) { +unsigned long real_size; + +if (sha1_object_info (e->idx.oid.hash, &real_size) <0 || + size != real_size) +die ("BUG: 'size' is supposed to be the object size!" ); + } } static inline unsigned long oe_delta_size(struct packing_data *pack,
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 1/11 pack-objects: a bit of document about struct object_entry
The role of this comment block becomes more important after we shuffle fields around to shrink this struct. It will be much harder to see what field is related to what.
--- pack-objects.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
pack-objects.h +45 -0
@@ -1,6 +1,51 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +/* + * basic object info + * ----------------- + * idx.oid is filled up before delta searching starts. idx.crc32 is + * only valid after the object is written out and will be used for + * generating the index. idx.offset will be both gradually set and + * used in writing phase (base objects get offset first, then deltas + * refer to them) + * + * "size" is the uncompressed object size. Compressed size of the raw + * data for an object in a pack is not stored anywhere but is computed + * and made available when reverse .idx is made. + * + * "hash" contains a path name hash which is used for sorting the + * delta list and also during delta searching. Once prepare_pack() + * returns it's no longer needed. + * + * source pack info + * ---------------- + * The (in_pack, in_pack_offset) tuple contains the location of the + * object in the source pack. in_pack_header_size allows quickly + * skipping the header and going straight to the zlib stream. + * + * "type" and "in_pack_type" both describe object type. in_pack_type + * may contain a delta type, while type is always the canonical type. + * + * deltas + * ------ + * Delta links (delta, delta_child and delta_sibling) are created to + * reflect that delta graph from the source pack then updated or added + * during delta searching phase when we find better deltas. + * + * delta_child and delta_sibling are last needed in + * compute_write_order(). "delta" and "delta_size" must remain valid + * at object writing phase in case the delta is not cached. + * + * If a delta is cached in memory and is compressed, delta_data points + * to the data and z_delta_size contains the compressed size. If it's + * uncompressed [1], z_delta_size must be zero. delta_size is always + * the uncompressed size and must be valid even if the delta is not + * cached. + * + * [1] during try_delta phase we don't bother with compressing because + * the delta could be quickly replaced with a better one. + */ struct object_entry { struct pack_idx_entry idx; unsigned long size; /* uncompressed size */
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 2/11 pack-objects: turn type and in_pack_type to bitfields
In reply to Nguyễn Thái Ngọc DuyAn extra field type_valid is added to carry the equivalent of OBJ_BAD in the original "type" field. in_pack_type always contains a valid type so we only need 3 bits for it.
A note about accepting OBJ_NONE as "valid" type. The function read_object_list_from_stdin() can pass this value [1] and it eventually calls create_object_entry() where current code skip setting "type" field if the incoming type is zero. This does not have any bad side effects because "type" field should be memset()'d anyway.
But since we also need to set type_valid now, skipping oe_set_type() leaves type_valid zero/false, which will make oe_type() return OBJ_BAD, not OBJ_NONE anymore. Apparently we do care about OBJ_NONE in prepare_pack(). This switch from OBJ_NONE to OBJ_BAD may trigger
fatal: unable to get type of object ...
Accepting OBJ_NONE [2] does sound wrong, but this is how it is has been for a very long time and I haven't time to dig in further.
[1] See 5c49c11686 (pack-objects: better check_object() performances -
2007-04-16)
[2] 21666f1aae (convert object type handling from a string to a number
- 2007-02-26)
--- builtin/pack-objects.c | 60 ++++++++++++++++++++++++------------------ cache.h | 2 ++ object.h | 1 - pack-bitmap-write.c | 6 ++--- pack-objects.h | 20 ++++++++++++-- 5 files changed, 58 insertions(+), 31 deletions(-)
builtin/pack-objects.c +35 -25
@@ -265,7 +265,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstruct git_istream *st =NULL ;if (!usable_delta) { -if (entry->type ==OBJ_BLOB && +if (oe_type (entry) ==OBJ_BLOB && entry->size > big_file_threshold && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;
@@ -371,7 +371,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset; -enum object_type type = entry->type; +enum object_type type =oe_type (entry);off_t datalen;unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];
@@ -480,11 +480,12 @@ static off_t write_object(struct hashfile *f, to_reuse =0 ;/* explicit */ else if (!entry->in_pack) to_reuse =0 ;/* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA)/* check_object() decided it for us ... */ to_reuse = usable_delta;/* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ else if (entry->delta) to_reuse =0 ;/* we want to pack afresh */
@@ -705,8 +706,8 @@ static struct object_entry **compute_write_order(void)* And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_COMMIT && - objects[i].type !=OBJ_TAG ) +if (oe_type (&objects[i]) !=OBJ_COMMIT && +oe_type (&objects[i]) !=OBJ_TAG )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -715,7 +716,7 @@ static struct object_entry **compute_write_order(void)* And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { -if (objects[i].type !=OBJ_TREE ) +if (oe_type (&objects[i]) !=OBJ_TREE )continue ;add_to_write_order (wo, &wo_end, &objects[i]); }
@@ -1066,8 +1067,7 @@ static void create_object_entry(const struct object_id *oid, entry =packlist_alloc (&to_pack, oid->hash, index_pos); entry->hash = hash; -if (type) - entry->type = type; +oe_set_type (entry, type);if (exclude) entry->preferred_base =1 ;else
@@ -1407,6 +1407,7 @@ static void check_object(struct object_entry *entry)unsigned long avail;off_t ofs;unsigned char *buf, c; +enum object_type type; buf =use_pack (p, &w_curs, entry->in_pack_offset, &avail);
@@ -1415,11 +1416,15 @@ static void check_object(struct object_entry *entry)* since non-delta representations could still be reused. */ used =unpack_object_header_buffer (buf, avail, - &entry->in_pack_type, + &type, &entry->size);if (used ==0 ) goto give_up; +if (type <0 ) +die ("BUG: invalid type %d" , type); + entry->in_pack_type = type; +/* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as
@@ -1428,9 +1433,9 @@ static void check_object(struct object_entry *entry)switch (entry->in_pack_type) {default :/* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->in_pack_header_size = used; -if (entry->type <OBJ_COMMIT || entry->type >OBJ_BLOB ) +if (oe_type (entry) <OBJ_COMMIT ||oe_type (entry) >OBJ_BLOB ) goto give_up;unuse_pack (&w_curs);return ;
@@ -1484,7 +1489,7 @@ static void check_object(struct object_entry *entry)* deltify other objects against, in order to avoid * circular deltas. */ - entry->type = entry->in_pack_type; +oe_set_type (entry, entry->in_pack_type); entry->delta = base_entry; entry->delta_size = entry->size; entry->delta_sibling = base_entry->delta_child;
@@ -1493,7 +1498,7 @@ static void check_object(struct object_entry *entry)return ; } -if (entry->type) { +if (oe_type (entry)) {/* * This must be a delta and we already know what the * final object type is. Let's extract the actual
@@ -1516,7 +1521,7 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } - entry->type =sha1_object_info (entry->idx.oid.hash, &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored
@@ -1559,6 +1564,7 @@ static void drop_reused_delta(struct object_entry *entry) {struct object_entry **p = &entry->delta->delta_child;struct object_info oi =OBJECT_INFO_INIT ; +enum object_type type;while (*p) {if (*p == entry)
@@ -1570,16 +1576,18 @@ static void drop_reused_delta(struct object_entry *entry) entry->depth =0 ; oi.sizep = &entry->size; - oi.typep = &entry->type; + oi.typep = &type;if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy. -* And if that fails, the error will be recorded in entry->type +* And if that fails, the error will be recorded in oe_type(entry) * and dealt with in prepare_pack(). */ - entry->type =sha1_object_info (entry->idx.oid.hash, - &entry->size); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, + &entry->size)); + }else { +oe_set_type (entry, type); } }
@@ -1747,10 +1755,12 @@ static int type_size_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +enum object_type a_type =oe_type (a); +enum object_type b_type =oe_type (b); -if (a->type > b->type) +if (a_type > b_type)return -1 ; -if (a->type < b->type) +if (a_type < b_type)return 1 ;if (a->hash > b->hash)return -1 ;
@@ -1826,7 +1836,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,void *delta_buf;/* Don't bother doing diffs between different types */ -if (trg_entry->type != src_entry->type) +if (oe_type (trg_entry) !=oe_type (src_entry))return -1 ;/*
@@ -2432,11 +2442,11 @@ static void prepare_pack(int window, int depth)if (!entry->preferred_base) { nr_deltas++; -if (entry->type <0 ) +if (oe_type (entry) <0 )die ("unable to get type of object %s" ,oid_to_hex (&entry->idx.oid)); }else { -if (entry->type <0 ) { +if (oe_type (entry) <0 ) {/* * This object is not found, but we * don't have to include it anyway.
@@ -2545,7 +2555,7 @@ static void read_object_list_from_stdin(void)die ("expected object ID, got garbage:\n %s" , line);add_preferred_base_object (p +1 ); -add_object_entry (&oid,0 , p +1 ,0 ); +add_object_entry (&oid,OBJ_NONE , p +1 ,0 ); } }
cache.h +2 -0
@@ -373,6 +373,8 @@ extern void free_name_hash(struct index_state *istate);#define read_blob_data_from_cache (path, sz) read_blob_data_from_index(&the_index, (path), (sz))#endif +#define TYPE_BITS 3 +enum object_type {OBJ_BAD =-1 ,OBJ_NONE =0 ,
object.h +0 -1
@@ -25,7 +25,6 @@ struct object_array {#define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3/* * object flag allocation: * revision.h: 0---------10 26
pack-bitmap-write.c +3 -3
@@ -64,12 +64,12 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, entry->in_pack_pos = i; -switch (entry->type) { +switch (oe_type (entry)) {case OBJ_COMMIT :case OBJ_TREE :case OBJ_BLOB :case OBJ_TAG : - real_type = entry->type; + real_type =oe_type (entry);break ;default :
@@ -98,7 +98,7 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index,default :die ("Missing type information for %s (%d/%d)" ,oid_to_hex (&entry->idx.oid), real_type, - entry->type); +oe_type (entry)); } } }
pack-objects.h +18 -2
@@ -59,8 +59,9 @@ struct object_entry {void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ unsigned long z_delta_size;/* delta data size (compressed) */ -enum object_type type; -enum object_type in_pack_type;/* could be delta */ + unsigned type_:TYPE_BITS; + unsigned in_pack_type:TYPE_BITS; /* could be delta */ + unsigned type_valid:1;uint32_t hash;/* name hint hash */ unsigned int in_pack_pos;unsigned char in_pack_header_size;
@@ -123,4 +124,19 @@ static inline uint32_t pack_name_hash(const char *name) return hash; } +static inline enum object_type oe_type (const struct object_entry *e) +{ +return e->type_valid ? e->type_ :OBJ_BAD ; +} + +static inline void oe_set_type (struct object_entry *e, +enum object_type type) +{ +if (type >=OBJ_ANY ) +die ("BUG: OBJ_ANY cannot be set in pack-objects code" ); + + e->type_valid = type >=OBJ_NONE ; + e->type_ = (unsigned )type; +} +#endif
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 3/11 pack-objects: use bitfield for object_entry::dfs_state
In reply to Nguyễn Thái Ngọc Duy--- builtin/pack-objects.c | 3 +++ pack-objects.h | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-)
builtin/pack-objects.c +3 -0
@@ -3049,6 +3049,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; +if (DFS_NUM_STATES > (1 <<OE_DFS_STATE_BITS )) +die ("BUG: too many dfs states, increase OE_DFS_STATE_BITS" ); + check_replace_refs =0 ;reset_pack_idx_option (&pack_idx_opts);
pack-objects.h +17 -11
@@ -1,6 +1,21 @@#ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H +#define OE_DFS_STATE_BITS 2 + +/* +* State flags for depth-first search used for analyzing delta cycles. +* +* The depth is measured in delta-links to the base (so if A is a delta +* against B, then A has a depth of 1, and B a depth of 0). +*/ +enum dfs_state { +DFS_NONE =0 , +DFS_ACTIVE , +DFS_DONE , +DFS_NUM_STATES +}; +/* * basic object info * -----------------
@@ -73,19 +88,10 @@ struct object_entry { unsigned no_try_delta:1; unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ + unsigned dfs_state:OE_DFS_STATE_BITS; -/* -* State flags for depth-first search used for analyzing delta cycles. -* -* The depth is measured in delta-links to the base (so if A is a delta -* against B, then A has a depth of 1, and B a depth of 0). -*/ -enum { -DFS_NONE =0 , -DFS_ACTIVE , -DFS_DONE - } dfs_state;int depth; + };struct packing_data {
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 4/11 pack-objects: use bitfield for object_entry::depth
In reply to Nguyễn Thái Ngọc DuyBecause of struct packing from now on we can only handle max depth 4095 (or even lower when new booleans are added in this struct). This should be ok since long delta chain will cause significant slow down anyway.
--- Documentation/config.txt | 1 + Documentation/git-pack-objects.txt | 4 +++- Documentation/git-repack.txt | 4 +++- builtin/pack-objects.c | 4 ++++ pack-objects.h | 5 ++--- 5 files changed, 13 insertions(+), 5 deletions(-)
Documentation/config.txt +1 -0
@@ -2412,6 +2412,7 @@ pack.window:: pack.depth:: The maximum delta depth used by linkgit:git-pack-objects[1] when no maximum depth is given on the command line. Defaults to 50. + Maximum value is 4095. pack.windowMemory:: The maximum size of memory that is consumed by each thread
Documentation/git-pack-objects.txt +3 -1
@@ -96,7 +96,9 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --window-memory=<n>:: This option provides an additional limit on top of `--window`;
Documentation/git-repack.txt +3 -1
@@ -90,7 +90,9 @@ other objects in that pack they already have locally. space. `--depth` limits the maximum delta depth; making it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. - The default value for --window is 10 and --depth is 50. ++ +The default value for --window is 10 and --depth is 50. The maximum +depth is 4095. --threads=<n>:: This option is passed through to `git pack-objects`.
builtin/pack-objects.c +4 -0
@@ -3068,6 +3068,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (pack_to_stdout != !base_name || argc)usage_with_options (pack_usage, pack_objects_options); +if (depth >= (1 <<OE_DEPTH_BITS )) +die (_ ("delta chain depth %d is greater than maximum limit %d" ), + depth, (1 <<OE_DEPTH_BITS ) -1 ); +argv_array_push (&rp,"pack-objects" );if (thin) { use_internal_rev_list =1 ;
pack-objects.h +2 -3
@@ -2,6 +2,7 @@#define PACK_OBJECTS_H #define OE_DFS_STATE_BITS 2 +#define OE_DEPTH_BITS 12/* * State flags for depth-first search used for analyzing delta cycles.
@@ -89,9 +90,7 @@ struct object_entry { unsigned tagged:1; /* near the very tip of refs */ unsigned filled:1; /* assigned write-order */ unsigned dfs_state:OE_DFS_STATE_BITS; - -int depth; - + unsigned depth:OE_DEPTH_BITS; };struct packing_data {
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 5/11 pack-objects: move in_pack_pos out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyThis field is only need for pack-bitmap, which is an optional feature. Move it to a separate array that is only allocated when pack-bitmap is used (it's not freed in the same way that objects[] is not).
--- builtin/pack-objects.c | 3 ++- pack-bitmap-write.c | 8 +++++--- pack-bitmap.c | 2 +- pack-bitmap.h | 4 +++- pack-objects.h | 16 +++++++++++++++- 5 files changed, 26 insertions(+), 7 deletions(-)
builtin/pack-objects.c +2 -1
@@ -879,7 +879,8 @@ static void write_pack_file(void)if (write_bitmap_index) {bitmap_writer_set_checksum (oid.hash); -bitmap_writer_build_type_index (written_list, nr_written); +bitmap_writer_build_type_index ( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name,
pack-bitmap-write.c +5 -3
@@ -48,7 +48,8 @@ void bitmap_writer_show_progress(int show)/** * Build the initial type index for the packfile */ -void bitmap_writer_build_type_index (struct pack_idx_entry **index, +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index,uint32_t index_nr) {uint32_t i;
@@ -57,12 +58,13 @@ void bitmap_writer_build_type_index(struct pack_idx_entry **index, writer.trees =ewah_new (); writer.blobs =ewah_new (); writer.tags =ewah_new (); +ALLOC_ARRAY (to_pack->in_pack_pos, to_pack->nr_objects);for (i =0 ; i < index_nr; ++i) {struct object_entry *entry = (struct object_entry *)index[i];enum object_type real_type; - entry->in_pack_pos = i; +oe_set_in_pack_pos (to_pack, entry, i);switch (oe_type (entry)) {case OBJ_COMMIT :
@@ -147,7 +149,7 @@ static uint32_t find_object_pos(const unsigned char *sha1) "(object %s is missing)", sha1_to_hex(sha1)); } -return entry->in_pack_pos; +return oe_in_pack_pos (writer.to_pack, entry); }static void show_object (struct object *object,const char *name,void *data)
pack-bitmap.c +1 -1
@@ -1032,7 +1032,7 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, oe =packlist_find (mapping, sha1,NULL );if (oe) - reposition[i] = oe->in_pack_pos +1 ; + reposition[i] =oe_in_pack_pos (mapping, oe) +1 ; } rebuild =bitmap_new ();
pack-bitmap.h +3 -1
@@ -44,7 +44,9 @@ int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bivoid bitmap_writer_show_progress (int show);void bitmap_writer_set_checksum (unsigned char *sha1); -void bitmap_writer_build_type_index (struct pack_idx_entry **index,uint32_t index_nr); +void bitmap_writer_build_type_index (struct packing_data *to_pack, +struct pack_idx_entry **index, +uint32_t index_nr);void bitmap_writer_reuse_bitmaps (struct packing_data *to_pack);void bitmap_writer_select_commits (struct commit **indexed_commits,unsigned int indexed_commits_nr,int max_bitmaps);
pack-objects.h +15 -1
@@ -79,7 +79,6 @@ struct object_entry { unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;uint32_t hash;/* name hint hash */ -unsigned int in_pack_pos;unsigned char in_pack_header_size; unsigned preferred_base:1; /** we do not pack this, but is available
@@ -99,6 +98,8 @@ struct packing_data {int32_t *index;uint32_t index_size; + +unsigned int *in_pack_pos; }; struct object_entry *packlist_alloc(struct packing_data *pdata,
@@ -144,4 +145,17 @@ static inline void oe_set_type(struct object_entry *e, e->type_ = (unsigned)type; } +static inline unsigned int oe_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e) +{ +return pack->in_pack_pos[e - pack->objects]; +} + +static inline void oe_set_in_pack_pos (const struct packing_data *pack, +const struct object_entry *e, +unsigned int pos) +{ + pack->in_pack_pos[e - pack->objects] = pos; +} +#endif
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 6/11 pack-objects: move in_pack out of struct object_entry
In reply to Nguyễn Thái Ngọc DuyInstead of using 8 bytes (on 64 bit arch) to store a pointer to a pack. Use an index instead since the number of packs should be relatively small.
This limits the number of packs we can handle to 16k. For now if you hit 16k pack files limit, pack-objects will simply fail [1].
[1] The escape hatch is .keep file to limit the non-kept pack files below 16k limit. Then you can go for another pack-objects run to combine another 16k pack files. Repeat until you're satisfied.
--- Documentation/git-pack-objects.txt | 9 ++++++ builtin/pack-objects.c | 40 +++++++++++++++++---------- cache.h | 1 + pack-objects.h | 44 +++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 15 deletions(-)
Documentation/git-pack-objects.txt +9 -0
@@ -269,6 +269,15 @@ Unexpected missing object will raise an error. locally created objects [without .promisor] and objects from the promisor remote [with .promisor].) This is used with partial clone. +LIMITATIONS +----------- + +This command could only handle 16384 existing pack files at a time. +If you have more than this, you need to exclude some pack files with +".keep" file and --honor-pack-keep option, to combine 16k pack files +in one, then remove these .keep files and run pack-objects one more +time. + SEE ALSO -------- linkgit:git-rev-list[1]
builtin/pack-objects.c +26 -14
@@ -29,6 +29,8 @@#include "list.h" #include "packfile.h" +#define IN_PACK (obj) oe_in_pack(&to_pack, obj) +static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),N_ ("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]" ),
@@ -367,7 +369,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entstatic off_t write_reuse_object (struct hashfile *f,struct object_entry *entry,unsigned long limit,int usable_delta) { -struct packed_git *p = entry->in_pack; +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;struct revindex_entry *revidx;off_t offset;
@@ -478,7 +480,7 @@ static off_t write_object(struct hashfile *f,if (!reuse_object) to_reuse =0 ;/* explicit */ -else if (!entry->in_pack) +else if (!IN_PACK (entry)) to_reuse =0 ;/* can't reuse what we don't have */ else if (oe_type (entry) ==OBJ_REF_DELTA ||oe_type (entry) ==OBJ_OFS_DELTA )
@@ -1025,7 +1027,7 @@ static int want_object_in_pack(const struct object_id *oid,if (*found_pack) { want =want_found_object (exclude, *found_pack);if (want !=-1 ) -return want; + goto done; }list_for_each (pos, &packed_git_mru) {
@@ -1048,11 +1050,16 @@ static int want_object_in_pack(const struct object_id *oid,if (!exclude && want >0 )list_move (&p->mru, &packed_git_mru);if (want !=-1 ) -return want; + goto done; } } -return 1 ; + want =1 ; +done: +if (want && *found_pack && !(*found_pack)->index) +oe_add_pack (&to_pack, *found_pack); + +return want; } static void create_object_entry(const struct object_id *oid,
@@ -1074,7 +1081,7 @@ static void create_object_entry(const struct object_id *oid,else nr_result++;if (found_pack) { - entry->in_pack = found_pack; +oe_set_in_pack (entry, found_pack); entry->in_pack_offset = found_offset; }
@@ -1399,8 +1406,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { -if (entry->in_pack) { -struct packed_git *p = entry->in_pack; +if (IN_PACK (entry)) { +struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;const unsigned char *base_ref =NULL ;struct object_entry *base_entry;
@@ -1535,14 +1542,16 @@ static int pack_offset_sort(const void *_a, const void *_b) {const struct object_entry *a = *(struct object_entry **)_a;const struct object_entry *b = *(struct object_entry **)_b; +const struct packed_git *a_in_pack =IN_PACK (a); +const struct packed_git *b_in_pack =IN_PACK (b);/* avoid filesystem trashing with loose objects */ -if (!a->in_pack && !b->in_pack) +if (!a_in_pack && !b_in_pack)return oidcmp (&a->idx.oid, &b->idx.oid); -if (a->in_pack < b->in_pack) +if (a_in_pack < b_in_pack)return -1 ; -if (a->in_pack > b->in_pack) +if (a_in_pack > b_in_pack)return 1 ;return a->in_pack_offset < b->in_pack_offset ?-1 : (a->in_pack_offset > b->in_pack_offset);
@@ -1578,7 +1587,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &entry->size; oi.typep = &type; -if (packed_object_info (entry->in_pack, entry->in_pack_offset, &oi) <0 ) { +if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/* * We failed to get the info from this pack for some reason; * fall back to sha1_object_info, which may find another copy.
@@ -1848,8 +1857,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,* it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ -if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && +if (reuse_delta &&IN_PACK (trg_entry) && +IN_PACK (trg_entry) ==IN_PACK (src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type !=OBJ_REF_DELTA && trg_entry->in_pack_type !=OBJ_OFS_DELTA )
@@ -3191,6 +3200,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } +/* make sure IN_PACK(0) return NULL */ +oe_add_pack (&to_pack,NULL ); +if (progress) progress_state =start_progress (_ ("Counting objects" ),0 );if (!use_internal_rev_list)
cache.h +1 -0
@@ -1635,6 +1635,7 @@ extern struct packed_git {int index_version;time_t mtime;int pack_fd; +int index;/* for builtin/pack-objects.c */ unsigned pack_local:1, pack_keep:1, freshened:1,
pack-objects.h +43 -1
72 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 7/11 pack-objects: refer to delta objects by index instead of pointer
In reply to Nguyễn Thái Ngọc DuyThese delta pointers always point to elements in the objects[] array in packing_data struct. We can only hold maximum 4G of those objects because the array size in nr_objects is uint32_t. We could use uint32_t indexes to address these elements instead of pointers. On 64-bit architecture (8 bytes per pointer) this would save 4 bytes per pointer.
Convert these delta pointers to indexes. Since we need to handle NULL pointers as well, the index is shifted by one [1].
[1] This means we can only index 2^32-2 objects even though nr_objects could contain 2^32-1 objects. It should not be a problem in practice because when we grow objects[], nr_alloc would probably blow up long before nr_objects hits the wall.
--- builtin/pack-objects.c | 116 ++++++++++++++++++++++------------------- pack-objects.h | 67 ++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 59 deletions(-)
builtin/pack-objects.c +62 -54
@@ -30,6 +30,12 @@#include "packfile.h" #define IN_PACK (obj) oe_in_pack(&to_pack, obj) +#define DELTA (obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD (obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING (obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA (obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_CHILD (obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING (obj, val) oe_set_delta_sibling(&to_pack, obj, val)static const char *pack_usage[] = {N_ ("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]" ),
@@ -127,11 +133,11 @@ static void *get_delta(struct object_entry *entry) buf =read_sha1_file (entry->idx.oid.hash, &type, &size);if (!buf)die ("unable to read %s" ,oid_to_hex (&entry->idx.oid)); - base_buf =read_sha1_file (entry->delta->idx.oid.hash, &type, + base_buf =read_sha1_file (DELTA (entry)->idx.oid.hash, &type, &base_size);if (!base_buf)die ("unable to read %s" , -oid_to_hex (&entry->delta->idx.oid)); +oid_to_hex (&DELTA (entry)->idx.oid)); delta_buf =diff_delta (base_buf, base_size, buf, size, &delta_size,0 );if (!delta_buf || delta_size != entry->delta_size)
@@ -288,12 +294,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent size = entry->delta_size; buf = entry->delta_data; entry->delta_data =NULL ; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }else { buf =get_delta (entry); size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; }
@@ -317,7 +323,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent* encoding of the relative offset for the delta * base from this object's position in the pack. */ -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -343,7 +349,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entreturn 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; }else {if (limit && hdrlen + datalen +20 >= limit) {
@@ -379,8 +385,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; -if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? +if (DELTA (entry)) + type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), type, entry->size);
@@ -408,7 +414,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, }if (type ==OBJ_OFS_DELTA ) { -off_t ofs = entry->idx.offset - entry->delta->idx.offset; +off_t ofs = entry->idx.offset -DELTA (entry)->idx.offset;unsigned pos =sizeof (dheader) -1 ; dheader[pos] = ofs &127 ;while (ofs >>=7 )
@@ -427,7 +433,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,return 0 ; }hashwrite (f, header, hdrlen); -hashwrite (f, entry->delta->idx.oid.hash,20 ); +hashwrite (f,DELTA (entry)->idx.oid.hash,20 ); hdrlen +=20 ; reused_delta++; }else {
@@ -467,13 +473,13 @@ static off_t write_object(struct hashfile *f,else limit = pack_size_limit - write_offset; -if (!entry->delta) +if (!DELTA (entry)) usable_delta =0 ;/* no delta */ else if (!pack_size_limit) usable_delta =1 ;/* unlimited packfile */ -else if (entry->delta->idx.offset == (off_t)-1 ) +else if (DELTA (entry)->idx.offset == (off_t)-1 ) usable_delta =0 ;/* base was written to another pack */ -else if (entry->delta->idx.offset) +else if (DELTA (entry)->idx.offset) usable_delta =1 ;/* base already exists in this pack */ else usable_delta =0 ;/* base could end up in another pack */
@@ -489,7 +495,7 @@ static off_t write_object(struct hashfile *f,/* ... but pack split may override that */ else if (oe_type(entry) != entry->in_pack_type) to_reuse =0 ;/* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse =0 ;/* we want to pack afresh */ else to_reuse =1 ;/* we have it in-pack undeltified,
@@ -541,12 +547,12 @@ static enum write_one_status write_one(struct hashfile *f, }/* if we are deltified, write out base object first. */ -if (e->delta) { +if (DELTA (e)) { e->idx.offset =1 ;/* now recurse */ -switch (write_one (f, e->delta, offset)) { +switch (write_one (f,DELTA (e), offset)) {case WRITE_ONE_RECURSIVE :/* we cannot depend on this one */ - e->delta =NULL ; +SET_DELTA (e,NULL );break ;default :break ;
@@ -608,34 +614,34 @@ static void add_descendants_to_write_order(struct object_entry **wo,/* add this node... */ add_to_write_order (wo, endp, e);/* all its siblings... */ -for (s = e->delta_sibling; s; s = s->delta_sibling) { +for (s =DELTA_SIBLING (e); s; s =DELTA_SIBLING (s)) {add_to_write_order (wo, endp, s); } }/* drop down a level to add left subtree nodes if possible */ -if (e->delta_child) { +if (DELTA_CHILD (e)) { add_to_order =1 ; - e = e->delta_child; + e =DELTA_CHILD (e); }else { add_to_order =0 ;/* our sibling might have some children, it is next */ -if (e->delta_sibling) { - e = e->delta_sibling; +if (DELTA_SIBLING (e)) { + e =DELTA_SIBLING (e);continue ; }/* go back to our parent node */ - e = e->delta; -while (e && !e->delta_sibling) { + e =DELTA (e); +while (e && !DELTA_SIBLING (e)) {/* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e =DELTA (e); }if (!e) {/* done- we hit our original root node */ return ; }/* pass it off to sibling at this level */ - e = e->delta_sibling; + e =DELTA_SIBLING (e); } }; }
@@ -646,7 +652,7 @@ static void add_family_to_write_order(struct object_entry **wo, {struct object_entry *root; -for (root = e; root->delta; root = root->delta) +for (root = e;DELTA (root); root =DELTA (root)) ;/* nothing */ add_descendants_to_write_order (wo, endp, root); }
@@ -661,8 +667,8 @@ static struct object_entry **compute_write_order(void)for (i =0 ; i < to_pack.nr_objects; i++) { objects[i].tagged =0 ; objects[i].filled =0 ; - objects[i].delta_child =NULL ; - objects[i].delta_sibling =NULL ; +SET_DELTA_CHILD (&objects[i],NULL ); +SET_DELTA_SIBLING (&objects[i],NULL ); }/*
@@ -672,11 +678,11 @@ static struct object_entry **compute_write_order(void)*/ for (i = to_pack.nr_objects; i >0 ;) {struct object_entry *e = &objects[--i]; -if (!e->delta) +if (!DELTA (e))continue ;/* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; + e->delta_sibling_idx =DELTA (e)->delta_child_idx; +SET_DELTA_CHILD (DELTA (e), e); }/*
@@ -1498,10 +1504,10 @@ static void check_object(struct object_entry *entry)* circular deltas. */ oe_set_type (entry, entry->in_pack_type); - entry->delta = base_entry; +SET_DELTA (entry, base_entry); entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + entry->delta_sibling_idx = base_entry->delta_child_idx; +SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);return ; }
@@ -1572,17 +1578,19 @@ static int pack_offset_sort(const void *_a, const void *_b)*/ static void drop_reused_delta (struct object_entry *entry) { -struct object_entry **p = &entry->delta->delta_child; +unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; -while (*p) { -if (*p == entry) - *p = (*p)->delta_sibling; +while (*idx) { +struct object_entry *oe = &to_pack.objects[*idx -1 ]; + +if (oe == entry) + *idx = oe->delta_sibling_idx;else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta =NULL ; +SET_DELTA (entry,NULL ); entry->depth =0 ; oi.sizep = &entry->size;
@@ -1622,7 +1630,7 @@ static void break_delta_chains(struct object_entry *entry)for (cur = entry, total_depth =0 ; cur; - cur = cur->delta, total_depth++) { + cur =DELTA (cur), total_depth++) {if (cur->dfs_state ==DFS_DONE ) {/* * We've already seen this object and know it isn't
@@ -1647,7 +1655,7 @@ static void break_delta_chains(struct object_entry *entry)* it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ -if (!cur->delta) { +if (!DELTA (cur)) { cur->dfs_state =DFS_DONE ;break ; }
@@ -1670,7 +1678,7 @@ static void break_delta_chains(struct object_entry *entry)* We keep all commits in the chain that we examined. */ cur->dfs_state =DFS_ACTIVE ; -if (cur->delta->dfs_state ==DFS_ACTIVE ) { +if (DELTA (cur)->dfs_state ==DFS_ACTIVE ) {drop_reused_delta (cur); cur->dfs_state =DFS_DONE ;break ;
@@ -1685,7 +1693,7 @@ static void break_delta_chains(struct object_entry *entry)* an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next =DELTA (cur);/* * We should have a chain of zero or more ACTIVE states down to
@@ -1870,7 +1878,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,/* Now some size filtering heuristics. */ trg_size = trg_entry->size; -if (!trg_entry->delta) { +if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ; }else {
@@ -1946,7 +1954,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,if (!delta_buf)return 0 ; -if (trg_entry->delta) { +if (DELTA (trg_entry)) {/* Prefer only shallower same-sized deltas. */ if (delta_size == trg_entry->delta_size && src->depth +1 >= trg->depth) {
@@ -1975,7 +1983,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,free (delta_buf); } - trg_entry->delta = src_entry; +SET_DELTA (trg_entry, src_entry); trg_entry->delta_size = delta_size; trg->depth = src->depth +1 ;
@@ -1984,13 +1992,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,static unsigned int check_delta_limit (struct object_entry *me,unsigned int n) { -struct object_entry *child = me->delta_child; +struct object_entry *child =DELTA_CHILD (me);unsigned int m = n;while (child) {unsigned int c =check_delta_limit (child, n +1 );if (m < c) m = c; - child = child->delta_sibling; + child =DELTA_SIBLING (child); }return m; }
@@ -2059,7 +2067,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* otherwise they would become too deep. */ max_depth = depth; -if (entry->delta_child) { +if (DELTA_CHILD (entry)) { max_depth -=check_delta_limit (entry,0 );if (max_depth <=0 ) goto next;
@@ -2109,7 +2117,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* depth, leaving it in the window is pointless. we * should evict it first. */ -if (entry->delta && max_depth <= n->depth) +if (DELTA (entry) && max_depth <= n->depth)continue ;/*
@@ -2117,7 +2125,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ -if (entry->delta) { +if (DELTA (entry)) {struct unpacked swap = array[best_base];int dist = (window + idx - best_base) % window;int dst = best_base;
@@ -2438,7 +2446,7 @@ static void prepare_pack(int window, int depth)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = to_pack.objects + i; -if (entry->delta) +if (DELTA (entry))/* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */
pack-objects.h +62 -5
77 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 8/11 pack-objects: shrink z_delta_size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyWe only cache deltas when it's smaller than a certain limit. This limit defaults to 1000 but save its compressed length in a 64-bit field. Shrink that field down to 16 bits, so you can only cache 65kb deltas. Larger deltas must be recomputed at when the pack is written down.
--- Documentation/config.txt | 3 ++- builtin/pack-objects.c | 22 ++++++++++++++++------ pack-objects.h | 3 ++- 3 files changed, 20 insertions(+), 8 deletions(-)
Documentation/config.txt +2 -1
@@ -2449,7 +2449,8 @@ pack.deltaCacheLimit:: The maximum size of a delta, that is cached in linkgit:git-pack-objects[1]. This cache is used to speed up the writing object phase by not having to recompute the final delta - result once the best match for all objects is found. Defaults to 1000. + result once the best match for all objects is found. + Defaults to 1000. Maximum value is 65535. pack.threads:: Specifies the number of threads to spawn when searching for best
builtin/pack-objects.c +16 -6
@@ -2105,12 +2105,19 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,* between writes at that moment. */ if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size =do_compress (&entry->delta_data, - entry->delta_size); -cache_lock (); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; -cache_unlock (); +unsigned long size; + + size =do_compress (&entry->delta_data, entry->delta_size); +if (size < (1 <<OE_Z_DELTA_BITS )) { + entry->z_delta_size = size; +cache_lock (); + delta_cache_size -= entry->delta_size; + delta_cache_size += entry->z_delta_size; +cache_unlock (); + }else { +FREE_AND_NULL (entry->delta_data); + entry->z_delta_size =0 ; + } }/* if we made n a delta, and if n is already at max
@@ -3089,6 +3096,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)if (depth >= (1 <<OE_DEPTH_BITS ))die (_ ("delta chain depth %d is greater than maximum limit %d" ), depth, (1 <<OE_DEPTH_BITS ) -1 ); +if (cache_max_small_delta_size >= (1 <<OE_Z_DELTA_BITS )) +die (_ ("pack.deltaCacheLimit is greater than maximum limit %d" ), + (1 <<OE_Z_DELTA_BITS ) -1 );argv_array_push (&rp,"pack-objects" );if (thin) {
pack-objects.h +2 -1
@@ -4,6 +4,7 @@#define OE_DFS_STATE_BITS 2#define OE_DEPTH_BITS 12#define OE_IN_PACK_BITS 14 +#define OE_Z_DELTA_BITS 16/* * State flags for depth-first search used for analyzing delta cycles.
@@ -79,7 +80,7 @@ struct object_entry {*/ void *delta_data;/* cached delta (uncompressed) */ unsigned long delta_size;/* delta data size (uncompressed) */ -unsigned long z_delta_size;/* delta data size (compressed) */ + unsigned z_delta_size:OE_Z_DELTA_BITS; unsigned type_:TYPE_BITS; unsigned in_pack_type:TYPE_BITS; /* could be delta */ unsigned type_valid:1;
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
PATCH v6 9/11 pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc DuyIt's very very rare that an uncompressedd object is larger than 4GB (partly because Git does not handle those large files very well to begin with). Let's optimize it for the common case where object size is smaller than this limit.
Shrink size field down to 32 bits [1] and one overflow bit. If the size is too large, we read it back from disk.
Add two compare helpers that can take advantage of the overflow bit (e.g. if the file is 4GB+, chances are it's already larger than core.bigFileThreshold and there's no point in comparing the actual value).
A small note about the conditional oe_set_size() in check_object(). Technically if we don't get a valid type, it's not wrong if we set uninitialized value "size" (we don't pre-initialize this and sha1_object_info will not assign anything when it fails to get the info).
This how changes the writing code path slightly which emits different error messages (either way we die). One of our tests in t5530 depends on this specific error message. Let's just keep the test as-is and play safe by not assigning random value. That might trigger valgrind anyway.
[1] it's actually already 32 bits on Windows
--- builtin/pack-objects.c | 49 ++++++++++++++++++++++------------- pack-objects.h | 58 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 19 deletions(-)
builtin/pack-objects.c +31 -18
@@ -274,7 +274,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entif (!usable_delta) {if (oe_type (entry) ==OBJ_BLOB && - entry->size > big_file_threshold && +oe_size_greater_than (entry, big_file_threshold) && (st =open_istream (entry->idx.oid.hash, &type, &size,NULL )) !=NULL ) buf =NULL ;else {
@@ -384,12 +384,13 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,unsigned char header[MAX_PACK_OBJECT_HEADER ], dheader[MAX_PACK_OBJECT_HEADER ];unsigned hdrlen; +unsigned long entry_size =oe_size (entry);if (DELTA (entry)) type = (allow_ofs_delta &&DELTA (entry)->idx.offset) ?OBJ_OFS_DELTA :OBJ_REF_DELTA ; hdrlen =encode_in_pack_object_header (header,sizeof (header), - type, entry->size); + type, entry_size); offset = entry->in_pack_offset; revidx =find_pack_revindex (p, offset);
@@ -406,7 +407,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size;if (!pack_to_stdout && p->index_version ==1 && -check_pack_inflate (p, &w_curs, offset, datalen, entry->size)) { +check_pack_inflate (p, &w_curs, offset, datalen, entry_size)) {error ("corrupt packed object for %s" ,oid_to_hex (&entry->idx.oid));unuse_pack (&w_curs);
@@ -1412,6 +1413,8 @@ static void cleanup_preferred_base(void)static void check_object (struct object_entry *entry) { +unsigned long size; +if (IN_PACK (entry)) {struct packed_git *p =IN_PACK (entry);struct pack_window *w_curs =NULL ;
@@ -1431,13 +1434,14 @@ static void check_object(struct object_entry *entry)*/ used =unpack_object_header_buffer (buf, avail, &type, - &entry->size); + &size);if (used ==0 ) goto give_up;if (type <0 )die ("BUG: invalid type %d" , type); entry->in_pack_type = type; +oe_set_size (entry, size);/* * Determine if this is a delta and if so whether we can
@@ -1505,7 +1509,7 @@ static void check_object(struct object_entry *entry)*/ oe_set_type (entry, entry->in_pack_type);SET_DELTA (entry, base_entry); - entry->delta_size = entry->size; + entry->delta_size =oe_size (entry); entry->delta_sibling_idx = base_entry->delta_child_idx;SET_DELTA_CHILD (base_entry, entry);unuse_pack (&w_curs);
@@ -1513,14 +1517,17 @@ static void check_object(struct object_entry *entry) }if (oe_type (entry)) { +unsigned long size; + + size =get_size_from_delta (p, &w_curs, + entry->in_pack_offset + entry->in_pack_header_size);/* * This must be a delta and we already know what the * final object type is. Let's extract the actual * object size from the delta header. */ - entry->size =get_size_from_delta (p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); -if (entry->size ==0 ) +oe_set_size (entry, size); +if (oe_size_less_than (entry,1 )) goto give_up;unuse_pack (&w_curs);return ;
@@ -1535,13 +1542,15 @@ static void check_object(struct object_entry *entry)unuse_pack (&w_curs); } -oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &entry->size)); +oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, &size));/* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored * as a preferred base. Doing so can result in a larger * pack file, but the transfer will still take place. */ +if (entry->type_valid) +oe_set_size (entry, size); }static int pack_offset_sort (const void *_a,const void *_b)
@@ -1581,6 +1590,7 @@ static void drop_reused_delta(struct object_entry *entry)unsigned *idx = &to_pack.objects[entry->delta_idx -1 ].delta_child_idx;struct object_info oi =OBJECT_INFO_INIT ;enum object_type type; +unsigned long size;while (*idx) {struct object_entry *oe = &to_pack.objects[*idx -1 ];
@@ -1593,7 +1603,7 @@ static void drop_reused_delta(struct object_entry *entry)SET_DELTA (entry,NULL ); entry->depth =0 ; - oi.sizep = &entry->size; + oi.sizep = &size; oi.typep = &type;if (packed_object_info (IN_PACK (entry), entry->in_pack_offset, &oi) <0 ) {/*
@@ -1603,10 +1613,11 @@ static void drop_reused_delta(struct object_entry *entry)* and dealt with in prepare_pack(). */ oe_set_type (entry,sha1_object_info (entry->idx.oid.hash, - &entry->size)); + &size)); }else {oe_set_type (entry, type); } +oe_set_size (entry, size); }/*
@@ -1746,7 +1757,7 @@ static void get_object_details(void)for (i =0 ; i < to_pack.nr_objects; i++) {struct object_entry *entry = sorted_by_offset[i];check_object (entry); -if (big_file_threshold < entry->size) +if (oe_size_greater_than (entry, big_file_threshold)) entry->no_try_delta =1 ; }
@@ -1775,6 +1786,8 @@ static int type_size_sort(const void *_a, const void *_b)const struct object_entry *b = *(struct object_entry **)_b;enum object_type a_type =oe_type (a);enum object_type b_type =oe_type (b); +unsigned long a_size =oe_size (a); +unsigned long b_size =oe_size (b);if (a_type > b_type)return -1 ;
@@ -1788,9 +1801,9 @@ static int type_size_sort(const void *_a, const void *_b)return -1 ;if (a->preferred_base < b->preferred_base)return 1 ; -if (a->size > b->size) +if (a_size > b_size)return -1 ; -if (a->size < b->size) +if (a_size < b_size)return 1 ;return a < b ?-1 : (a > b);/* newest first */ }
@@ -1877,7 +1890,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,return 0 ;/* Now some size filtering heuristics. */ - trg_size = trg_entry->size; + trg_size =oe_size (trg_entry);if (!DELTA (trg_entry)) { max_size = trg_size/2 -20 ; ref_depth =1 ;
@@ -1889,7 +1902,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, (max_depth - ref_depth +1 );if (max_size ==0 )return 0 ; - src_size = src_entry->size; + src_size =oe_size (src_entry); sizediff = src_size < trg_size ? trg_size - src_size :0 ;if (sizediff >= max_size)return 0 ;
@@ -2009,7 +2022,7 @@ static unsigned long free_unpacked(struct unpacked *n)free_delta_index (n->index); n->index =NULL ;if (n->data) { - freed_mem += n->entry->size; + freed_mem +=oe_size (n->entry);FREE_AND_NULL (n->data); } n->entry =NULL ;
@@ -2459,7 +2472,7 @@ static void prepare_pack(int window, int depth)*/ continue ; -if (entry->size <50 ) +if (oe_size_less_than (entry,50 ))continue ;if (entry->no_try_delta)
pack-objects.h +57 -1
68 lines not on this page · raw message
Signature · 1 line
2.17.0.rc0.347.gf9cf61673a
Re: [PATCH v6 09/11] pack-objects: shrink size field in struct object_entry
On Sun, Mar 18 2018, Nguyễn Thái Ngọc Duy jotted:
Quoted 1 line
It's very very rare that an uncompressedd object is larger than 4GB
So this went from a typo of "uncompressd" in v5 to "uncompressedd", needs one less "d": "uncompressed".
Re: [PATCH v6 09/11] pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc Duypack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 280
Quoted 6 lines · Nguyễn Thái Ngọc Duy
pack-objects.h +6
-0+static inline int oe_fits_in_32bits (unsigned long limit) +{ +uint32_t truncated_limit = (uint32_t )limit; + +return limit == truncated_limit; +}
I do not think it is worth a reroll (there only are a few callsites), but the above has nothing to do with "oe" fitting anything (it is about "limit"). Do you mind if I did this instead?
static inline int fits_in_32bits(unsigned long size)
... or other suggestions, perhaps?
Re: [PATCH v6 09/11] pack-objects: shrink size field in struct object_entry
Quoted 13 lines · Junio C Hamano
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 280
Quoted 6 lines · Nguyễn Thái Ngọc Duy
pack-objects.h +6
-0+static inline int oe_fits_in_32bits (unsigned long limit) +{ +uint32_t truncated_limit = (uint32_t )limit; + +return limit == truncated_limit; +}I do not think it is worth a reroll (there only are a few callsites), but the above has nothing to do with "oe" fitting anything (it is about "limit"). Do you mind if I did this instead?
Quoted 4 lines
static inline int
fits_in_32bits(unsigned long size)... or other suggestions, perhaps?
I just tried to not pollute the general namespace too much. That works for me too.
Signature · 1 line
Duy
Re: [PATCH v6 09/11] pack-objects: shrink size field in struct object_entry
In reply to Nguyễn Thái Ngọc Duypack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 306
Quoted 5 lines · Nguyễn Thái Ngọc Duy
pack-objects.h +5
-0+static inline void oe_set_size (struct object_entry *e, +unsigned long size) +{ + e->size_ = size; + e->size_valid = e->size_ == size;
A quite similar comment as my earlier one applies here. I wonder if this is easier to read?
e->size_valid = fits_in_32bits(size); if (e->size_valid) e->size_ = size;
Stepping back a bit in a different tangent,
- fits_in_32bits() is a good public name if the helper is about seeing if the given quantity fits in 32bit uint,
- but that carves it in stone that our e->size_ *will* be 32bit forever, which is not good.
So, it may be a good idea to call it size_cacheable_in_oe(size) or something to ask "I have this 'size'; is it small enough to fit in the field in the oe, i.e. allow us to cache it, as opposed to having to go back to the object every time?" Of course, this would declare that the helper can only be used for that particular field, but that is sort of the point of such a change, to allow us to later define the e->size_ field to different sizes without affecting other stuff.
pack-objects.h static inline void oe_set_delta_sibling(struct packing_data *pack, · line 314
Quoted 7 lines
pack-objects.h +7
-0+if (!e->size_valid) { +unsigned long real_size; + +if (sha1_object_info (e->idx.oid.hash, &real_size) <0 || + size != real_size) +die ("BUG: 'size' is supposed to be the object size!" ); + }
If an object that is smaller than 4GB is fed to this function with an incorrect size, we happily record it in e->size_ and declare it is valid. Wouldn't that be equally grave error as we are catching in this block?
Quoted 3 lines
pack-objects.h +2
-0+} +#endif