git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 2/2] strbuf: allow to use preallocated memory

From
WDWilliam Duclot <william.duclot@ensimag.grenoble-inp.fr>
Date
May 30, 2016, 14:15 UTC
Message-ID
<1639412597.204503.1464617754937.JavaMail.zimbra@ensimag.grenoble-inp.fr>
In-Reply-To
<vpqlh2remhy.fsf@anie.imag.fr>
Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
Show 19 quoted lines
> William Duclot <william.duclot@ensimag.grenoble-inp.fr> writes:
> 
>> @@ -20,16 +28,37 @@ char strbuf_slopbuf[1];
>>  
>>  void strbuf_init(struct strbuf *sb, size_t hint)
>>  {
>> +	sb->flags = 0;
>>  	sb->alloc = sb->len = 0;
>>  	sb->buf = strbuf_slopbuf;
>>  	if (hint)
>>  		strbuf_grow(sb, hint);
>>  }
> 
> If you set flags = 0 here, existing callers will have all flags off,
> including OWNS_MEMORY.
> 
> I *think* this is OK, as sb->buf is currently pointing to
> strbuf_slopbuf, which the the strbuf doesn't own. But that is too subtle
> to go without an explanatory comment IMHO.
Right
Show 35 quoted lines
> 
> Also, doesn't this make the "new_buf" case useless in strbuf_grow?
> 
> With your patch, the code looks like:
> 
> void strbuf_grow(struct strbuf *sb, size_t extra)
> {
> 	int new_buf = !sb->alloc;
> ...
> 	if (sb->flags & STRBUF_OWNS_MEMORY) {
> 		if (new_buf) // <---------------------------------------- (1)
> 			sb->buf = NULL;
> 		ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
> 	} else {
> 		/*
> 		 * The strbuf doesn't own the buffer: to avoid to realloc it,
> 		 * the strbuf needs to use a new buffer without freeing the old
> 		 */
> 		if (sb->len + extra + 1 > sb->alloc) {
> 			size_t new_alloc = MAX(sb->len + extra + 1, alloc_nr(sb->alloc));
> 			char *buf = xmalloc(new_alloc);
> 			memcpy(buf, sb->buf, sb->alloc);
> 			sb->buf = buf;
> 			sb->alloc = new_alloc;
> 			sb->flags |= STRBUF_OWNS_MEMORY;
> 		}
> 	}
> 
> 	if (new_buf) // <---------------------------------------- (2)
> 		sb->buf[0] = '\0';
> }
> 
> I think (1) is now dead code, since sb->alloc == 0 implies that
> STRBUF_OWNS_MEMORY is set. (2) seems redundant since you've just
> memcpy-ed the existing '\0' into the buffer.

You're right for (1), I hadn't noticed that. For (2), we'll still have to set sb->buf[new_alloc-1]='\0' after the memcpy, if we have sb->alloc==0 then the memcpy won't copy it.

Show 23 quoted lines
>> +void strbuf_wrap_preallocated(struct strbuf *sb, char *path_buf,
>> +			      size_t path_buf_len, size_t alloc_len)
>> +{
>> +	if (!path_buf)
>> +		die("you try to use a NULL buffer to initialize a strbuf");
>> +
>> +	strbuf_init(sb, 0);
>> +	strbuf_attach(sb, path_buf, path_buf_len, alloc_len);
>> +	sb->flags &= ~STRBUF_OWNS_MEMORY;
>> +	sb->flags &= ~STRBUF_FIXED_MEMORY;
>> +}
> 
> strbuf_wrap_preallocated seem very close to strbuf_attach. I'd rather
> see a symmetric code sharing like
> 
> void strbuf_attach_internal(struct strbuf *sb, ..., unsigned int flags)
> 
> and then strbuf_attach() and strbuf_wrap_preallocated() become
> straightforward wrappers around it.
> 
> This would avoid setting and then unsetting STRBUF_OWNS_MEMORY (the
> performance impact is probably small, but it looks weird to set the flag
> and then unset it right away).
We'll refactor the code with Johannes' remarks and yours in mind
Show 8 quoted lines
> After your patch, there are differences between
> strbuf_wrap_preallocated() which I think are inconsistencies:
> 
> * strbuf_attach() does not check for NULL buffer, but it doesn't accept
>   them either if I read correctly. It would make sense to add the check
>   to strbuf_attach(), but it's weird to have the performance-critical
>   oriented function do the expensive stuff that the
>   non-performance-critical one doesn't.

I agree that strbuf_attach should do the check (it seems strange that it doesn't already do it, as the "buffer never NULL" invariant is not new). I don't understand your "but" part, what "expensive stuff" are you talking about?

Show 16 quoted lines
> * strbuf_attach() calls strbuf_release(), which allows reusing an
>   existing strbuf. strbuf_wrap_preallocated() calls strbuf_init which
>   would override silently any previous content. I think strbuf_attach()
>   does the right thing here.
>
>   In any case, you probably want to include calls to strbuf_attach() and
>   strbuf_wrap_*() functions on existing non-empty strbufs.
>
>> +void strbuf_wrap_fixed(struct strbuf *sb, char *path_buf,
>> +		       size_t path_buf_len, size_t alloc_len)
>> +{
>> +	strbuf_wrap_preallocated(sb, path_buf, path_buf_len, alloc_len);
>> +	sb->flags |= STRBUF_FIXED_MEMORY;
>> +}
> 
> And this could become a 3rd caller of strbuf_attach_internal().
True. We'll take care of that when refactoring
Show 33 quoted lines
>> @@ -61,9 +96,32 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>>  	if (unsigned_add_overflows(extra, 1) ||
>>  	    unsigned_add_overflows(sb->len, extra + 1))
>>  		die("you want to use way too much memory");
>> -	if (new_buf)
>> -		sb->buf = NULL;
>> -	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
>> +	if ((sb->flags & STRBUF_FIXED_MEMORY) && sb->len + extra + 1 > sb->alloc)
>> +		die("you try to make a string overflow the buffer of a fixed strbuf");
>> +
>> +	/*
>> +	 * ALLOC_GROW may do a realloc() if needed, so we must not use it on
>> +	 * a buffer the strbuf doesn't own
>> +	 */
>> +	if (sb->flags & STRBUF_OWNS_MEMORY) {
>> +		if (new_buf)
>> +			sb->buf = NULL;
>> +		ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
>> +	} else {
>> +		/*
>> +		 * The strbuf doesn't own the buffer: to avoid to realloc it,
>> +		 * the strbuf needs to use a new buffer without freeing the old
>> +		 */
>> +		if (sb->len + extra + 1 > sb->alloc) {
>> +			size_t new_alloc = MAX(sb->len + extra + 1, alloc_nr(sb->alloc));
>> +			char *buf = xmalloc(new_alloc);
>> +			memcpy(buf, sb->buf, sb->alloc);
> 
> I think you want to memcpy only sb->len + 1 bytes. Here, you're
> memcpy-ing the allocated-but-not-initialized part of the array.
> 
> xmemdupz can probably simplify the code too (either you include the '\0'
> in what memcpy copies, or you let xmemdupz add it).

memcpying sb->len+1 bytes is indeed enough. xmemdupz can only allocate the same size it will copy. Here we want to allocate `new_alloc` bytes but only copy `sb->alloc` bytes (or sb->len+1 bytes, as you noticed)

Show 7 quoted lines
>> +/**
>> + * Allow the caller to give a pre-allocated piece of memory for the strbuf
>> + * to use. It is possible then to strbuf_grow() the string past the size
>> of the
>> + * pre-allocated buffer: a new buffer will be allocated. The pre-allocated
> 
> To make it clearer: "a new buffer will then be allocated"?
OK
Show 11 quoted lines
>> +/**
>> + * Allow the caller to give a pre-allocated piece of memory for the strbuf
>> + * to use and indicate that the strbuf must use exclusively this buffer,
>> + * never realloc() it or allocate a new one. It means that the string can
>> + * be manipulated but cannot overflow the pre-allocated buffer. The
>> + * pre-allocated buffer will never be freed.
>> + */
> 
> Perhaps say explicitly that although the allocated buffer has a fixed
> size, the string itself can grow as long as it does not overflow the
> buffer?

That's what I meant by "the string can be manipulated but cannot overflow the pre-allocated buffer". I'll try to reformulate

Show 9 quoted lines
>> @@ -91,6 +116,8 @@ extern void strbuf_release(struct strbuf *);
>>   * Detach the string from the strbuf and returns it; you now own the
>>   * storage the string occupies and it is your responsibility from then on
>>   * to release it with `free(3)` when you are done with it.
>> + * Must allocate a copy of the buffer in case of a preallocated/fixed
>> buffer.
>> + * Performance-critical operations have to be aware of this.
> 
> Better than just warn about performance, you can give the alternative.

I'm not sure what you mean, I don't think there really is an alternative for detaching a string?

Previous: Matthieu MoyNext: Matthieu Moy
Message 16 of 38 in “strbuf: improve API”
  1. 0/2 strbuf: improve APIWilliam Duclot, May 30, 2016
  2. 1/2 strbuf: add testsWilliam Duclot, May 30, 2016
  3. Johannes SchindelinMay 30, 2016
  4. Simon RabourgMay 30, 2016
  5. Matthieu MoyMay 30, 2016
  6. Michael HaggertyMay 31, 2016
  7. Simon RabourgMay 31, 2016
  8. 2/2 strbuf: allow to use preallocated memoryWilliam Duclot, May 30, 2016
  9. Johannes SchindelinMay 30, 2016
  10. William DuclotMay 30, 2016
  11. Johannes SchindelinMay 31, 2016
  12. Michael HaggertyMay 31, 2016
  13. Johannes SchindelinMay 31, 2016
  14. Michael HaggertyMay 31, 2016
  15. Matthieu MoyMay 30, 2016
  16. William DuclotMay 30, 2016
  17. Matthieu MoyMay 30, 2016
  18. William DuclotMay 30, 2016
  19. Michael HaggertyMay 31, 2016
  20. William DuclotMay 31, 2016
  21. William DuclotJun 3, 2016
  22. Mike HommeyMay 30, 2016
  23. William DuclotMay 30, 2016
  24. Mike HommeyMay 30, 2016
  25. Junio C HamanoMay 31, 2016
  26. WilliamMay 31, 2016
  27. Matthieu MoyMay 31, 2016
  28. William DuclotMay 31, 2016
  29. Remi Galan AlfonsoMay 30, 2016
  30. Jeff KingJun 1, 2016
  31. David TurnerJun 1, 2016
  32. Jeff KingJun 1, 2016
  33. David TurnerJun 1, 2016
  34. Jeff KingJun 1, 2016
  35. Michael HaggertyJun 2, 2016
  36. Matthieu MoyJun 2, 2016
  37. William DuclotJun 2, 2016
  38. Jeff KingJun 24, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.