On Sat, Mar 24, 2018 at 07:33:51AM +0100, Nguyễn Thái Ngọc Duy wrote:

> It's very very rare that an uncompressed 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. As noted in the previous
> patch, we need to return the delta size instead of canonical size when
> the to-be-reused object entry type is a delta instead of a canonical
> one.
> 
> 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).
> 
> Another note about oe_get_size_slow(). This function MUST be thread
> safe because SIZE() macro is used inside try_delta() which may run in
> parallel. Outside parallel code, no-contention locking should be dirt
> cheap (or insignificant compared to i/o access anyway). To exercise
> this code, it's best to run the test suite with something like
> 
>     make test GIT_TEST_OE_SIZE_BITS=2
> 
> which forces this code on all objects larger than 3 bytes.

OK, makes sense. Since we need it to be thread-safe, we have to use
read_lock(). Which means that oe_get_size_slow() is defined in
builtin/pack-objects.c. But the object_entry is defined in the
more-generic pack-objects.h.

So anybody besides builtin/pack-objects.c will have to implement their
own fallback when e->size_valid isn't true. Which is a little odd, but I
guess nobody else needs that field. It might bite us in the future, but
I'm willing to cross my fingers for now (the pack-objects.h header is
really just there to support the bitmap writing code, but even that
could in theory all get shoved into a single translation unit if we had
to).

> [1] it's actually already 32 bits on Windows

And linux-i386. :)

> +unsigned long oe_get_size_slow(struct packing_data *pack,
> +                            const struct object_entry *e)
> +{

I think I already replied about this earlier, so I'll skim over it this
time.

> diff --git a/pack-objects.c b/pack-objects.c
> index 13f2b2bff2..59c6e40a02 100644
> --- a/pack-objects.c
> +++ b/pack-objects.c
> @@ -120,8 +120,15 @@ struct object_entry *packlist_alloc(struct packing_data 
> *pdata,
>  {
>       struct object_entry *new_entry;
>  
> -     if (!pdata->nr_objects)
> +     if (!pdata->nr_objects) {
>               prepare_in_pack_by_idx(pdata);
> +             if (getenv("GIT_TEST_OE_SIZE_BITS")) {
> +                     int bits = atoi(getenv("GIT_TEST_OE_SIZE_BITS"));;
> +                     pdata->oe_size_limit = 1 << bits;
> +             }
> +             if (!pdata->oe_size_limit)
> +                     pdata->oe_size_limit = 1 << OE_SIZE_BITS;
> +     }

This needs to be "1U << OE_SIZE_BITS". Shifting a signed integer 31 bits
is undefined.

No, I'm not that clever or careful myself. I ran the whole test suite
with SANITIZE=address,undefined and it turned this up, as well as a
similar case for OE_DELTA_SIZE_BITS.

> +     uint32_t size_:OE_SIZE_BITS;
> +     uint32_t size_valid:1;

A uint32_t bitfield? Would it make more sense to just call these
"unsigned", since we're specifying the precision already?

> +unsigned long oe_get_size_slow(struct packing_data *pack,
> +                            const struct object_entry *e);
> +static inline unsigned long oe_size(struct packing_data *pack,
> +                                 const struct object_entry *e)
> +{
> +     if (e->size_valid)
> +             return e->size_;
> +
> +     return oe_get_size_slow(pack, e);
> +}

If oe_get_size_slow() fails to find an object's size, it dies. I'm
trying to think of whether that might hit funny corner cases with
racing. I don't _think_ so, because if the object truly goes away, we'd
be screwed during the writing phase anyway.

> +static inline int oe_size_less_than(struct packing_data *pack,
> +                                 const struct object_entry *lhs,
> +                                 unsigned long rhs)
> +{
> +     if (lhs->size_valid)
> +             return lhs->size_ < rhs;
> +     if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
> +             return 0;
> +     return oe_get_size_slow(pack, lhs) < rhs;
> +}

Clever.

> +static inline void oe_set_size(struct packing_data *pack,
> +                            struct object_entry *e,
> +                            unsigned long size)
> +{
> +     if (size < pack->oe_size_limit) {
> +             e->size_ = size;
> +             e->size_valid = 1;
> +     } else {
> +             e->size_valid = 0;
> +             if (oe_get_size_slow(pack, e) != size)
> +                     die("BUG: 'size' is supposed to be the object size!");
> +     }
> +}

That's an expensive assertion. But I guess this isn't supposed to happen
very frequently, so it's probably OK.

-Peff

Reply via email to