diff mbox series

[FFmpeg-devel,3/8] avutil/mem: Add av_fast_realloc_array()

Message ID DB6PR0101MB22144C833D5F308F85DCE5588F819@DB6PR0101MB2214.eurprd01.prod.exchangelabs.com
State New
Headers show
Series [FFmpeg-devel,1/8] avutil/mem: Handle fast allocations near UINT_MAX properly | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Andreas Rheinhardt July 5, 2022, 8:26 p.m. UTC
From: Andreas Rheinhardt <andres.rheinhardt@outlook.com>

This is an array-equivalent of av_fast_realloc(). Its advantages
compared to using av_fast_realloc() for allocating arrays are as
follows:

a) It performs its own overflow checks for the multiplication that is
implicit in array allocations. (And it only needs to perform these
checks (as well as the multiplication itself) in case the array needs to
be reallocated.)
b) It allows to limit the number of elements to an upper bound given
by the caller. This allows to restrict the number of allocated elements
to fit into an int and therefore makes this function usable with
counters of this type. It can also be used to avoid overflow checks in
the caller: E.g. setting it to UINT_MAX - 1 elements makes it safe to
increase the desired number of elements in steps of one. And it avoids
overallocations in situations where one already has an upper bound.
c) av_fast_realloc_array() will always allocate in multiples of array
elements; no memory is wasted with partial elements.
d) By returning an int, av_fast_realloc_array() can distinguish between
ordinary allocation failures (meriting AVERROR(ENOMEM)) and failures
because of allocation limits (by returning AVERROR(ERANGE)).
e) It is no longer possible for the user to accidentally lose the
pointer by using ptr = av_fast_realloc(ptr, ...).

Because of e) there is no need to set the number of allocated elements
to zero on failure.

av_fast_realloc() usually allocates size + size / 16 + 32 bytes if size
bytes are desired and if the already existing buffer isn't big enough.
av_fast_realloc_array() instead allocates nb + (nb + 14) / 16. Rounding
up is done in order not to reallocate in steps of one if the current
number is < 16; adding 14 instead of 15 has the effect of only
allocating one element if one element is desired. This is done with an
eye towards applications where arrays might commonly only contain one
element (as happens with the Matroska CueTrackPositions).

Which of the two functions allocates faster depends upon the size of
the elements. E.g. if the elements have a size of 32B and the desired
size is incremented in steps of one, allocations happen at
1, 3, 5, 7, 9, 11, 13, 15, 17, 20, 23, 26 ... for av_fast_realloc(),
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 21, 24 ... for
av_fast_realloc_array(). For element sizes of 96B, the numbers are
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30 ...
for av_fast_realloc() whereas the pattern for av_fast_realloc_array() is
unchanged.

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
---
This patch (and some of the other patches of this patchset)
are mostly the same as the one in these threads:
https://ffmpeg.org/pipermail/ffmpeg-devel/2019-December/254836.html
https://ffmpeg.org/pipermail/ffmpeg-devel/2020-January/255182.html

 doc/APIchanges      |  3 +++
 libavutil/mem.c     | 33 +++++++++++++++++++++++++++++++++
 libavutil/mem.h     | 30 ++++++++++++++++++++++++++++++
 libavutil/version.h |  2 +-
 4 files changed, 67 insertions(+), 1 deletion(-)

Comments

Tomas Härdin July 6, 2022, 2:40 p.m. UTC | #1
tis 2022-07-05 klockan 22:26 +0200 skrev Andreas Rheinhardt:
> From: Andreas Rheinhardt <andres.rheinhardt@outlook.com>
> 
> This is an array-equivalent of av_fast_realloc(). Its advantages
> compared to using av_fast_realloc() for allocating arrays are as
> follows:
> 
> a) It performs its own overflow checks for the multiplication that is
> implicit in array allocations. (And it only needs to perform these
> checks (as well as the multiplication itself) in case the array needs
> to
> be reallocated.)
> b) It allows to limit the number of elements to an upper bound given
> by the caller. This allows to restrict the number of allocated
> elements
> to fit into an int and therefore makes this function usable with
> counters of this type. It can also be used to avoid overflow checks
> in
> the caller: E.g. setting it to UINT_MAX - 1 elements makes it safe to
> increase the desired number of elements in steps of one. And it
> avoids
> overallocations in situations where one already has an upper bound.
> c) av_fast_realloc_array() will always allocate in multiples of array
> elements; no memory is wasted with partial elements.
> d) By returning an int, av_fast_realloc_array() can distinguish
> between
> ordinary allocation failures (meriting AVERROR(ENOMEM)) and failures
> because of allocation limits (by returning AVERROR(ERANGE)).
> e) It is no longer possible for the user to accidentally lose the
> pointer by using ptr = av_fast_realloc(ptr, ...).

If you add an option for clearing the newly allocated memory then this
could work for my av_fast_recalloc() use case in the jpeg2000 decoder.
Or we could have two functions.

Small bikeshed: since the function takes a pointer to a pointer as
argument, av_fast_realloc_arrayp() might be a better name. I had in
mind to similarly rename av_fast_recalloc() to av_fast_recallocp().


> +
> +    nb = min_nb + (min_nb + 14) / 16;

Not +15? Or +0?

> +
> +    /* If min_nb is so big that the above calculation overflowed,
> +     * just allocate as much as we are allowed to. */
> +    nb = nb < min_nb ? max_nb : FFMIN(nb, max_nb);

Looks OK, but an explicit check for overflow is easier to verify

> +
> +    memcpy(&array, ptr, sizeof(array));
> +
> +    array = av_realloc(array, nb * elsize);
> +    if (!array)
> +        return AVERROR(ENOMEM);
> +
> +    memcpy(ptr, &array, sizeof(array));

An optional memset() here would be useful for me

Else it looks OK

/Tomas
Andreas Rheinhardt July 6, 2022, 2:46 p.m. UTC | #2
Tomas Härdin:
> tis 2022-07-05 klockan 22:26 +0200 skrev Andreas Rheinhardt:
>> From: Andreas Rheinhardt <andres.rheinhardt@outlook.com>
>>
>> This is an array-equivalent of av_fast_realloc(). Its advantages
>> compared to using av_fast_realloc() for allocating arrays are as
>> follows:
>>
>> a) It performs its own overflow checks for the multiplication that is
>> implicit in array allocations. (And it only needs to perform these
>> checks (as well as the multiplication itself) in case the array needs
>> to
>> be reallocated.)
>> b) It allows to limit the number of elements to an upper bound given
>> by the caller. This allows to restrict the number of allocated
>> elements
>> to fit into an int and therefore makes this function usable with
>> counters of this type. It can also be used to avoid overflow checks
>> in
>> the caller: E.g. setting it to UINT_MAX - 1 elements makes it safe to
>> increase the desired number of elements in steps of one. And it
>> avoids
>> overallocations in situations where one already has an upper bound.
>> c) av_fast_realloc_array() will always allocate in multiples of array
>> elements; no memory is wasted with partial elements.
>> d) By returning an int, av_fast_realloc_array() can distinguish
>> between
>> ordinary allocation failures (meriting AVERROR(ENOMEM)) and failures
>> because of allocation limits (by returning AVERROR(ERANGE)).
>> e) It is no longer possible for the user to accidentally lose the
>> pointer by using ptr = av_fast_realloc(ptr, ...).
> 
> If you add an option for clearing the newly allocated memory then this
> could work for my av_fast_recalloc() use case in the jpeg2000 decoder.
> Or we could have two functions.
> 

I'd prefer it if the zeroing function were a wrapper around the
non-zeroing function.

> Small bikeshed: since the function takes a pointer to a pointer as
> argument, av_fast_realloc_arrayp() might be a better name. I had in
> mind to similarly rename av_fast_recalloc() to av_fast_recallocp().
> 
> 
>> +
>> +    nb = min_nb + (min_nb + 14) / 16;
> 
> Not +15? Or +0?

"av_fast_realloc_array() instead allocates nb + (nb + 14) / 16. Rounding
up is done in order not to reallocate in steps of one if the current
number is < 16; adding 14 instead of 15 has the effect of only
allocating one element if one element is desired."

> 
>> +
>> +    /* If min_nb is so big that the above calculation overflowed,
>> +     * just allocate as much as we are allowed to. */
>> +    nb = nb < min_nb ? max_nb : FFMIN(nb, max_nb);
> 
> Looks OK, but an explicit check for overflow is easier to verify
> 
>> +
>> +    memcpy(&array, ptr, sizeof(array));
>> +
>> +    array = av_realloc(array, nb * elsize);
>> +    if (!array)
>> +        return AVERROR(ENOMEM);
>> +
>> +    memcpy(ptr, &array, sizeof(array));
> 
> An optional memset() here would be useful for me
> 
> Else it looks OK
>
Tomas Härdin July 6, 2022, 2:54 p.m. UTC | #3
ons 2022-07-06 klockan 16:46 +0200 skrev Andreas Rheinhardt:
> Tomas Härdin:
> > tis 2022-07-05 klockan 22:26 +0200 skrev Andreas Rheinhardt:
> > > From: Andreas Rheinhardt <andres.rheinhardt@outlook.com>
> > > 
> > > This is an array-equivalent of av_fast_realloc(). Its advantages
> > > compared to using av_fast_realloc() for allocating arrays are as
> > > follows:
> > > 
> > > a) It performs its own overflow checks for the multiplication
> > > that is
> > > implicit in array allocations. (And it only needs to perform
> > > these
> > > checks (as well as the multiplication itself) in case the array
> > > needs
> > > to
> > > be reallocated.)
> > > b) It allows to limit the number of elements to an upper bound
> > > given
> > > by the caller. This allows to restrict the number of allocated
> > > elements
> > > to fit into an int and therefore makes this function usable with
> > > counters of this type. It can also be used to avoid overflow
> > > checks
> > > in
> > > the caller: E.g. setting it to UINT_MAX - 1 elements makes it
> > > safe to
> > > increase the desired number of elements in steps of one. And it
> > > avoids
> > > overallocations in situations where one already has an upper
> > > bound.
> > > c) av_fast_realloc_array() will always allocate in multiples of
> > > array
> > > elements; no memory is wasted with partial elements.
> > > d) By returning an int, av_fast_realloc_array() can distinguish
> > > between
> > > ordinary allocation failures (meriting AVERROR(ENOMEM)) and
> > > failures
> > > because of allocation limits (by returning AVERROR(ERANGE)).
> > > e) It is no longer possible for the user to accidentally lose the
> > > pointer by using ptr = av_fast_realloc(ptr, ...).
> > 
> > If you add an option for clearing the newly allocated memory then
> > this
> > could work for my av_fast_recalloc() use case in the jpeg2000
> > decoder.
> > Or we could have two functions.
> > 
> 
> I'd prefer it if the zeroing function were a wrapper around the
> non-zeroing function.

That should work, the change in allocated entries can be detected

> 
> > Small bikeshed: since the function takes a pointer to a pointer as
> > argument, av_fast_realloc_arrayp() might be a better name. I had in
> > mind to similarly rename av_fast_recalloc() to av_fast_recallocp().
> > 
> > 
> > > +
> > > +    nb = min_nb + (min_nb + 14) / 16;
> > 
> > Not +15? Or +0?
> 
> "av_fast_realloc_array() instead allocates nb + (nb + 14) / 16.
> Rounding
> up is done in order not to reallocate in steps of one if the current
> number is < 16; adding 14 instead of 15 has the effect of only
> allocating one element if one element is desired."

Wups, I actually read that part of the commit message then forgot about
it. I blame sickness

/Tomas
Andreas Rheinhardt July 12, 2022, 2:12 p.m. UTC | #4
Andreas Rheinhardt:
> From: Andreas Rheinhardt <andres.rheinhardt@outlook.com>
> 
> This is an array-equivalent of av_fast_realloc(). Its advantages
> compared to using av_fast_realloc() for allocating arrays are as
> follows:
> 
> a) It performs its own overflow checks for the multiplication that is
> implicit in array allocations. (And it only needs to perform these
> checks (as well as the multiplication itself) in case the array needs to
> be reallocated.)
> b) It allows to limit the number of elements to an upper bound given
> by the caller. This allows to restrict the number of allocated elements
> to fit into an int and therefore makes this function usable with
> counters of this type. It can also be used to avoid overflow checks in
> the caller: E.g. setting it to UINT_MAX - 1 elements makes it safe to
> increase the desired number of elements in steps of one. And it avoids
> overallocations in situations where one already has an upper bound.
> c) av_fast_realloc_array() will always allocate in multiples of array
> elements; no memory is wasted with partial elements.
> d) By returning an int, av_fast_realloc_array() can distinguish between
> ordinary allocation failures (meriting AVERROR(ENOMEM)) and failures
> because of allocation limits (by returning AVERROR(ERANGE)).
> e) It is no longer possible for the user to accidentally lose the
> pointer by using ptr = av_fast_realloc(ptr, ...).
> 
> Because of e) there is no need to set the number of allocated elements
> to zero on failure.
> 
> av_fast_realloc() usually allocates size + size / 16 + 32 bytes if size
> bytes are desired and if the already existing buffer isn't big enough.
> av_fast_realloc_array() instead allocates nb + (nb + 14) / 16. Rounding
> up is done in order not to reallocate in steps of one if the current
> number is < 16; adding 14 instead of 15 has the effect of only
> allocating one element if one element is desired. This is done with an
> eye towards applications where arrays might commonly only contain one
> element (as happens with the Matroska CueTrackPositions).
> 
> Which of the two functions allocates faster depends upon the size of
> the elements. E.g. if the elements have a size of 32B and the desired
> size is incremented in steps of one, allocations happen at
> 1, 3, 5, 7, 9, 11, 13, 15, 17, 20, 23, 26 ... for av_fast_realloc(),
> 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 21, 24 ... for
> av_fast_realloc_array(). For element sizes of 96B, the numbers are
> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 21, 23, 25, 27, 30 ...
> for av_fast_realloc() whereas the pattern for av_fast_realloc_array() is
> unchanged.
> 
> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
> ---
> This patch (and some of the other patches of this patchset)
> are mostly the same as the one in these threads:
> https://ffmpeg.org/pipermail/ffmpeg-devel/2019-December/254836.html
> https://ffmpeg.org/pipermail/ffmpeg-devel/2020-January/255182.html
> 
>  doc/APIchanges      |  3 +++
>  libavutil/mem.c     | 33 +++++++++++++++++++++++++++++++++
>  libavutil/mem.h     | 30 ++++++++++++++++++++++++++++++
>  libavutil/version.h |  2 +-
>  4 files changed, 67 insertions(+), 1 deletion(-)
> 
> diff --git a/doc/APIchanges b/doc/APIchanges
> index 20b944933a..f633ae6fee 100644
> --- a/doc/APIchanges
> +++ b/doc/APIchanges
> @@ -14,6 +14,9 @@ libavutil:     2021-04-27
>  
>  API changes, most recent first:
>  
> +2022-07-05 - xxxxxxxxxx - lavu 57.28.100 - mem.h
> +  Add av_fast_realloc_array() to simplify reallocating of arrays.
> +
>  2022-06-12 - xxxxxxxxxx - lavf 59.25.100 - avio.h
>    Add avio_vprintf(), similar to avio_printf() but allow to use it
>    from within a function taking a variable argument list as input.
> diff --git a/libavutil/mem.c b/libavutil/mem.c
> index 18aff5291f..6e3942ae63 100644
> --- a/libavutil/mem.c
> +++ b/libavutil/mem.c
> @@ -532,6 +532,39 @@ void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size)
>      return ptr;
>  }
>  
> +int av_fast_realloc_array(void *ptr, size_t *nb_allocated,
> +                          size_t min_nb, size_t max_nb, size_t elsize)
> +{
> +    void *array;
> +    size_t nb, max_alloc_size_bytes;
> +
> +    if (min_nb <= *nb_allocated)
> +        return 0;
> +
> +    max_alloc_size_bytes = atomic_load_explicit(&max_alloc_size, memory_order_relaxed);
> +    max_nb = FFMIN(max_nb, max_alloc_size_bytes / elsize);
> +
> +    if (min_nb > max_nb)
> +        return AVERROR(ERANGE);
> +
> +    nb = min_nb + (min_nb + 14) / 16;
> +
> +    /* If min_nb is so big that the above calculation overflowed,
> +     * just allocate as much as we are allowed to. */
> +    nb = nb < min_nb ? max_nb : FFMIN(nb, max_nb);
> +
> +    memcpy(&array, ptr, sizeof(array));
> +
> +    array = av_realloc(array, nb * elsize);
> +    if (!array)
> +        return AVERROR(ENOMEM);
> +
> +    memcpy(ptr, &array, sizeof(array));
> +    *nb_allocated = nb;
> +
> +    return 0;
> +}
> +
>  static inline void fast_malloc(void *ptr, unsigned int *size, size_t min_size, int zero_realloc)
>  {
>      size_t max_size;
> diff --git a/libavutil/mem.h b/libavutil/mem.h
> index d91174196c..f040de08fc 100644
> --- a/libavutil/mem.h
> +++ b/libavutil/mem.h
> @@ -375,11 +375,41 @@ int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
>   * @return `ptr` if the buffer is large enough, a pointer to newly reallocated
>   *         buffer if the buffer was not large enough, or `NULL` in case of
>   *         error
> + * @see av_fast_realloc_array()
>   * @see av_realloc()
>   * @see av_fast_malloc()
>   */
>  void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
>  
> +/**
> + * Reallocate the given array if it is not large enough, otherwise do nothing.
> + *
> + * If `ptr` points to `NULL`, then a new uninitialized array is allocated.
> + *
> + * @param[in,out] ptr           Pointer to `NULL` or pointer to an already
> + *                              allocated array. `*ptr` will be set to point
> + *                              to the new array on success.
> + * @param[in,out] nb_allocated  Pointer to the number of elements of the array
> + *                              `*ptr`. `*nb_allocated` is updated to the new
> + *                              number of allocated elements.
> + * @param[in]     min_nb        Desired minimal number of elements in array `*ptr`
> + * @param[in]     max_nb        Maximal number of elements to allocate.
> + * @param[in]     elsize        Size of a single element of the array.
> + *                              Must not be zero.
> + * @return 0 on success, < 0 on failure. On failure, `*ptr` is not freed and
> + *         `*ptr` as well as `*nb_allocated` are unchanged.
> + * @note `max_nb` can be used to limit allocations and make this function
> + *       usable with counters of types other than size_t. It can also be used
> + *       to avoid overflow checks in callers: E.g. setting it to `UINT_MAX - 1`
> + *       means that incrementing an unsigned int in steps of one need not be
> + *       checked for overflow.
> + * @see av_fast_realloc()
> + * @see av_realloc()
> + * @see av_fast_malloc()
> + */
> +int av_fast_realloc_array(void *ptr, size_t *nb_allocated,
> +                          size_t min_nb, size_t max_nb, size_t elsize);
> +
>  /**
>   * Allocate a buffer, reusing the given one if large enough.
>   *
> diff --git a/libavutil/version.h b/libavutil/version.h
> index 2e9e02dda8..87178e9e9a 100644
> --- a/libavutil/version.h
> +++ b/libavutil/version.h
> @@ -79,7 +79,7 @@
>   */
>  
>  #define LIBAVUTIL_VERSION_MAJOR  57
> -#define LIBAVUTIL_VERSION_MINOR  27
> +#define LIBAVUTIL_VERSION_MINOR  28
>  #define LIBAVUTIL_VERSION_MICRO 100
>  
>  #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \

Anton really dislikes the av_fast_* naming and instead wants this to be
called av_realloc_array_reuse(). I don't care either way. Any more
opinions on this (or on the patch itself)?

- Andreas
Anton Khirnov July 14, 2022, 8:14 a.m. UTC | #5
Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
> Anton really dislikes the av_fast_* naming and instead wants this to be
> called av_realloc_array_reuse(). I don't care either way. Any more
> opinions on this (or on the patch itself)?

If people dislike _reuse(), I am open to other reasonable suggestions.
This 'fast' naming sucks because
- it tells you nothing about how this function is "fast"
- it is added at the beginning rather than the end, which is
  against standard namespacing conventions
Andreas Rheinhardt July 14, 2022, 12:51 p.m. UTC | #6
Anton Khirnov:
> Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
>> Anton really dislikes the av_fast_* naming and instead wants this to be
>> called av_realloc_array_reuse(). I don't care either way. Any more
>> opinions on this (or on the patch itself)?
> 
> If people dislike _reuse(), I am open to other reasonable suggestions.
> This 'fast' naming sucks because
> - it tells you nothing about how this function is "fast"
> - it is added at the beginning rather than the end, which is
>   against standard namespacing conventions
> 

Isn't reusing the basic modus operandi for a reallocation function? So
your suggested name doesn't seem to fit either.
(The actual difference of this function to an ordinary function is that
it overallocates and does not shrink the buffer even if it is too big.)

- Andreas
Anton Khirnov July 17, 2022, 8:30 a.m. UTC | #7
Quoting Andreas Rheinhardt (2022-07-14 14:51:07)
> Anton Khirnov:
> > Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
> >> Anton really dislikes the av_fast_* naming and instead wants this to be
> >> called av_realloc_array_reuse(). I don't care either way. Any more
> >> opinions on this (or on the patch itself)?
> > 
> > If people dislike _reuse(), I am open to other reasonable suggestions.
> > This 'fast' naming sucks because
> > - it tells you nothing about how this function is "fast"
> > - it is added at the beginning rather than the end, which is
> >   against standard namespacing conventions
> > 
> 
> Isn't reusing the basic modus operandi for a reallocation function? So
> your suggested name doesn't seem to fit either.

Ordinary realloc just keeps the data, I wouldn't call that "reuse" since
it will often be a copy. This "fast" realloc OTOH reuses the actual
buffer, same as all the other "fast" mem.h functions.

But feel free to suggest another naming pattern if you can think of one.
Tomas Härdin July 21, 2022, 9:23 p.m. UTC | #8
tis 2022-07-12 klockan 16:12 +0200 skrev Andreas Rheinhardt:
> 
> Anton really dislikes the av_fast_* naming and instead wants this to
> be
> called av_realloc_array_reuse(). I don't care either way. Any more
> opinions on this (or on the patch itself)?

That's going to cause some "impedance mismatch" given that the other
functions are still called fast rather than reuse, and changing that
would requite a major bump.

Ideally we'd come up with a coherent naming scheme for all of these
functions because frankly it's a mess at the moment. I tried
summarizing all functions in a spreadsheet, and there's a lot of gaps
functionality wise. Best solution would be to write the cartesian
product of all features in one go, with an accompanying coherent naming
scheme.

{pointer, pointer-to-pointer} * {don't care what the data is, copy old
data, copy and zero newly allocated memory, zero entire buffer} * {size
in bytes, nelem*elemsize}

/Tomas
Anton Khirnov Aug. 17, 2022, 3:29 p.m. UTC | #9
Quoting Tomas Härdin (2022-07-21 23:23:25)
> tis 2022-07-12 klockan 16:12 +0200 skrev Andreas Rheinhardt:
> > 
> > Anton really dislikes the av_fast_* naming and instead wants this to
> > be
> > called av_realloc_array_reuse(). I don't care either way. Any more
> > opinions on this (or on the patch itself)?
> 
> That's going to cause some "impedance mismatch" given that the other
> functions are still called fast rather than reuse, and changing that
> would requite a major bump.

One of my other points was that the existing "fast" functions use
unsigned int rather than size_t, so that's another reason to replace
them.

Adding new functions and using them to replace all uses of the old ones
in our codebase does not need a major bump. Actually removing the old
ones does, but that can be done whenever.
Andreas Rheinhardt Sept. 26, 2022, 12:25 p.m. UTC | #10
Anton Khirnov:
> Quoting Andreas Rheinhardt (2022-07-14 14:51:07)
>> Anton Khirnov:
>>> Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
>>>> Anton really dislikes the av_fast_* naming and instead wants this to be
>>>> called av_realloc_array_reuse(). I don't care either way. Any more
>>>> opinions on this (or on the patch itself)?
>>>
>>> If people dislike _reuse(), I am open to other reasonable suggestions.
>>> This 'fast' naming sucks because
>>> - it tells you nothing about how this function is "fast"
>>> - it is added at the beginning rather than the end, which is
>>>   against standard namespacing conventions
>>>
>>
>> Isn't reusing the basic modus operandi for a reallocation function? So
>> your suggested name doesn't seem to fit either.
> 
> Ordinary realloc just keeps the data, I wouldn't call that "reuse" since
> it will often be a copy. This "fast" realloc OTOH reuses the actual
> buffer, same as all the other "fast" mem.h functions.
> 
> But feel free to suggest another naming pattern if you can think of one.
> 

I see two differences between this function and ordinary realloc: It
never shrinks the buffer and it overallocates. These two properties make
it more likely that these functions can avoid copies more often than
plain realloc (but in contrast to realloc, we can not grow the buffer in
case there is free space after it), but it is nevertheless the same as
realloc.

But I don't really care that much about the name and will therefore use
your name as I can't come up with anything better.
(Of course, I am still open to alternative suggestions.)

- Andreas
Andreas Rheinhardt Sept. 26, 2022, 2:21 p.m. UTC | #11
Andreas Rheinhardt:
> Anton Khirnov:
>> Quoting Andreas Rheinhardt (2022-07-14 14:51:07)
>>> Anton Khirnov:
>>>> Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
>>>>> Anton really dislikes the av_fast_* naming and instead wants this to be
>>>>> called av_realloc_array_reuse(). I don't care either way. Any more
>>>>> opinions on this (or on the patch itself)?
>>>>
>>>> If people dislike _reuse(), I am open to other reasonable suggestions.
>>>> This 'fast' naming sucks because
>>>> - it tells you nothing about how this function is "fast"
>>>> - it is added at the beginning rather than the end, which is
>>>>   against standard namespacing conventions
>>>>
>>>
>>> Isn't reusing the basic modus operandi for a reallocation function? So
>>> your suggested name doesn't seem to fit either.
>>
>> Ordinary realloc just keeps the data, I wouldn't call that "reuse" since
>> it will often be a copy. This "fast" realloc OTOH reuses the actual
>> buffer, same as all the other "fast" mem.h functions.
>>
>> But feel free to suggest another naming pattern if you can think of one.
>>
> 
> I see two differences between this function and ordinary realloc: It
> never shrinks the buffer and it overallocates. These two properties make
> it more likely that these functions can avoid copies more often than
> plain realloc (but in contrast to realloc, we can not grow the buffer in
> case there is free space after it), but it is nevertheless the same as
> realloc.
> 
> But I don't really care that much about the name and will therefore use
> your name as I can't come up with anything better.
> (Of course, I am still open to alternative suggestions.)
> 

Here is a rebased branch that uses av_realloc_array_reuse:
https://github.com/mkver/FFmpeg/commits/fast_realloc

- Andreas
Tomas Härdin Sept. 26, 2022, 2:24 p.m. UTC | #12
mån 2022-09-26 klockan 14:25 +0200 skrev Andreas Rheinhardt:
> Anton Khirnov:
> > Quoting Andreas Rheinhardt (2022-07-14 14:51:07)
> > > Anton Khirnov:
> > > > Quoting Andreas Rheinhardt (2022-07-12 16:12:16)
> > > > > Anton really dislikes the av_fast_* naming and instead wants
> > > > > this to be
> > > > > called av_realloc_array_reuse(). I don't care either way. Any
> > > > > more
> > > > > opinions on this (or on the patch itself)?
> > > > 
> > > > If people dislike _reuse(), I am open to other reasonable
> > > > suggestions.
> > > > This 'fast' naming sucks because
> > > > - it tells you nothing about how this function is "fast"
> > > > - it is added at the beginning rather than the end, which is
> > > >   against standard namespacing conventions
> > > > 
> > > 
> > > Isn't reusing the basic modus operandi for a reallocation
> > > function? So
> > > your suggested name doesn't seem to fit either.
> > 
> > Ordinary realloc just keeps the data, I wouldn't call that "reuse"
> > since
> > it will often be a copy. This "fast" realloc OTOH reuses the actual
> > buffer, same as all the other "fast" mem.h functions.
> > 
> > But feel free to suggest another naming pattern if you can think of
> > one.
> > 
> 
> I see two differences between this function and ordinary realloc: It
> never shrinks the buffer and it overallocates. These two properties
> make
> it more likely that these functions can avoid copies more often than
> plain realloc (but in contrast to realloc, we can not grow the buffer
> in
> case there is free space after it), but it is nevertheless the same
> as
> realloc.
> 
> But I don't really care that much about the name and will therefore
> use
> your name as I can't come up with anything better.
> (Of course, I am still open to alternative suggestions.)
> 
> - Andreas

So this means av_realloc_array_reuse()? Eh, it works. I will add a
function that also zeroes the newly allocated space, what should we
call that? av_realloc_array_reusez()?
av_realloc_array_reuse_zerofill()?

/Tomas
diff mbox series

Patch

diff --git a/doc/APIchanges b/doc/APIchanges
index 20b944933a..f633ae6fee 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -14,6 +14,9 @@  libavutil:     2021-04-27
 
 API changes, most recent first:
 
+2022-07-05 - xxxxxxxxxx - lavu 57.28.100 - mem.h
+  Add av_fast_realloc_array() to simplify reallocating of arrays.
+
 2022-06-12 - xxxxxxxxxx - lavf 59.25.100 - avio.h
   Add avio_vprintf(), similar to avio_printf() but allow to use it
   from within a function taking a variable argument list as input.
diff --git a/libavutil/mem.c b/libavutil/mem.c
index 18aff5291f..6e3942ae63 100644
--- a/libavutil/mem.c
+++ b/libavutil/mem.c
@@ -532,6 +532,39 @@  void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size)
     return ptr;
 }
 
+int av_fast_realloc_array(void *ptr, size_t *nb_allocated,
+                          size_t min_nb, size_t max_nb, size_t elsize)
+{
+    void *array;
+    size_t nb, max_alloc_size_bytes;
+
+    if (min_nb <= *nb_allocated)
+        return 0;
+
+    max_alloc_size_bytes = atomic_load_explicit(&max_alloc_size, memory_order_relaxed);
+    max_nb = FFMIN(max_nb, max_alloc_size_bytes / elsize);
+
+    if (min_nb > max_nb)
+        return AVERROR(ERANGE);
+
+    nb = min_nb + (min_nb + 14) / 16;
+
+    /* If min_nb is so big that the above calculation overflowed,
+     * just allocate as much as we are allowed to. */
+    nb = nb < min_nb ? max_nb : FFMIN(nb, max_nb);
+
+    memcpy(&array, ptr, sizeof(array));
+
+    array = av_realloc(array, nb * elsize);
+    if (!array)
+        return AVERROR(ENOMEM);
+
+    memcpy(ptr, &array, sizeof(array));
+    *nb_allocated = nb;
+
+    return 0;
+}
+
 static inline void fast_malloc(void *ptr, unsigned int *size, size_t min_size, int zero_realloc)
 {
     size_t max_size;
diff --git a/libavutil/mem.h b/libavutil/mem.h
index d91174196c..f040de08fc 100644
--- a/libavutil/mem.h
+++ b/libavutil/mem.h
@@ -375,11 +375,41 @@  int av_reallocp_array(void *ptr, size_t nmemb, size_t size);
  * @return `ptr` if the buffer is large enough, a pointer to newly reallocated
  *         buffer if the buffer was not large enough, or `NULL` in case of
  *         error
+ * @see av_fast_realloc_array()
  * @see av_realloc()
  * @see av_fast_malloc()
  */
 void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size);
 
+/**
+ * Reallocate the given array if it is not large enough, otherwise do nothing.
+ *
+ * If `ptr` points to `NULL`, then a new uninitialized array is allocated.
+ *
+ * @param[in,out] ptr           Pointer to `NULL` or pointer to an already
+ *                              allocated array. `*ptr` will be set to point
+ *                              to the new array on success.
+ * @param[in,out] nb_allocated  Pointer to the number of elements of the array
+ *                              `*ptr`. `*nb_allocated` is updated to the new
+ *                              number of allocated elements.
+ * @param[in]     min_nb        Desired minimal number of elements in array `*ptr`
+ * @param[in]     max_nb        Maximal number of elements to allocate.
+ * @param[in]     elsize        Size of a single element of the array.
+ *                              Must not be zero.
+ * @return 0 on success, < 0 on failure. On failure, `*ptr` is not freed and
+ *         `*ptr` as well as `*nb_allocated` are unchanged.
+ * @note `max_nb` can be used to limit allocations and make this function
+ *       usable with counters of types other than size_t. It can also be used
+ *       to avoid overflow checks in callers: E.g. setting it to `UINT_MAX - 1`
+ *       means that incrementing an unsigned int in steps of one need not be
+ *       checked for overflow.
+ * @see av_fast_realloc()
+ * @see av_realloc()
+ * @see av_fast_malloc()
+ */
+int av_fast_realloc_array(void *ptr, size_t *nb_allocated,
+                          size_t min_nb, size_t max_nb, size_t elsize);
+
 /**
  * Allocate a buffer, reusing the given one if large enough.
  *
diff --git a/libavutil/version.h b/libavutil/version.h
index 2e9e02dda8..87178e9e9a 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@ 
  */
 
 #define LIBAVUTIL_VERSION_MAJOR  57
-#define LIBAVUTIL_VERSION_MINOR  27
+#define LIBAVUTIL_VERSION_MINOR  28
 #define LIBAVUTIL_VERSION_MICRO 100
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \