"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: speed up reading of deltas a bit
To:
gameoftrees@openbsd.org
Date:
Sun, 29 May 2022 19:41:57 +0200

Download raw body.

Thread
No feedback on this one yet. Should I just go ahead?

On Fri, May 20, 2022 at 02:04:26PM +0200, Stefan Sperling wrote:
> Testing gotadmin pack with a pack file which contains proper
> deltas, performance bottlenecks show up elsewhere.
> Reading deltified tree objects is actually quite slow :-/
> 
> This patch speeds things up a little by not reading deltas twice
> during expansion. The old code first reads in all deltas to find
> the largest delta in the chain, then allocates appropriately-sized
> buffers to process the chain again and actually apply the deltas.
> The delta cache may help a little to avoid overhead from reading
> the same deltas again, but it is better to avoid doing any
> unnecessary work in the first place.
> 
> With this patch we do everything in one pass, and grow our buffers
> as needed when a larger delta appears somewhere in the chain.
> 
> In got_pack_dump_delta_chain_to_file() the same approach can be
> used to decide whether to apply a delta in memory or to a tempfile.
> We start out with memory buffers if possible and eventually switch
> to files if needed. For simplicity, once we have switched to files
> we won't go back to using memory buffers, even if delta sizes shrink.
> 
> More performance improvements will be needed, but this is a start. 
> 
> ok?
> 
> diff refs/heads/main refs/heads/delta-membuf
> blob - 4bac59b80e15c0c64099a61c98b1dd60cd3e01a4
> blob + 2c612a33c5d96977b8bbe4ac08c2fc41cc78381b
> --- lib/pack.c
> +++ lib/pack.c
> @@ -1257,34 +1257,25 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  	const struct got_error *err = NULL;
>  	struct got_delta *delta;
>  	uint8_t *base_buf = NULL, *accum_buf = NULL, *delta_buf;
> -	size_t base_bufsz = 0, accum_size = 0, delta_len;
> -	uint64_t max_size;
> -	int n = 0;
> -
> -	*result_size = 0;
> -
> -	if (STAILQ_EMPTY(&deltas->entries))
> -		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
> -
> +	size_t base_bufsz = 0, accum_bufsz = 0, accum_size = 0, delta_len;
>  	/* We process small enough files entirely in memory for speed. */
> -	err = got_pack_get_delta_chain_max_size(&max_size, deltas, pack);
> -	if (err)
> -		return err;
> -	if (max_size < GOT_DELTA_RESULT_SIZE_CACHED_MAX) {
> -		accum_buf = malloc(max_size);
> -		if (accum_buf == NULL)
> -			return got_error_from_errno("malloc");
> -		base_file = NULL;
> -		accum_file = NULL;
> -	} else {
> -		if (fseeko(base_file, 0L, SEEK_SET) == -1)
> -			return got_error_from_errno("fseeko");
> -		if (fseeko(accum_file, 0L, SEEK_SET) == -1)
> -			return got_error_from_errno("fseeko");
> -	}
> +	const size_t max_bufsize = GOT_DELTA_RESULT_SIZE_CACHED_MAX;
> +	uint64_t max_size = 0;
> +	int n = 0;
>  
> +	*result_size = 0;
> +
> +	if (STAILQ_EMPTY(&deltas->entries))
> +		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
> +
> +	if (fseeko(base_file, 0L, SEEK_SET) == -1)
> +		return got_error_from_errno("fseeko");
> +	if (fseeko(accum_file, 0L, SEEK_SET) == -1)
> +		return got_error_from_errno("fseeko");
> +
>  	/* Deltas are ordered in ascending order. */
>  	STAILQ_FOREACH(delta, &deltas->entries, entry) {
> +		uint64_t base_size, result_size = 0;
>  		int cached = 1;
>  		if (n == 0) {
>  			size_t mapoff;
> @@ -1311,7 +1302,9 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  					goto done;
>  				}
>  			}
> -			if (base_file) {
> +			if (delta->size > max_size)
> +				max_size = delta->size;
> +			if (max_size > max_bufsize) {
>  				if (pack->map) {
>  					mapoff = (size_t)delta_data_offset;
>  					err = got_inflate_to_file_mmap(
> @@ -1323,6 +1316,12 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  					    &base_bufsz, NULL, NULL, pack->fd,
>  					    base_file);
>  			} else {
> +				accum_buf = malloc(max_size);
> +				if (accum_buf == NULL) {
> +					err = got_error_from_errno("malloc");
> +					goto done;
> +				}
> +				accum_bufsz = max_size;
>  				if (pack->map) {
>  					mapoff = (size_t)delta_data_offset;
>  					err = got_inflate_to_mem_mmap(&base_buf,
> @@ -1337,7 +1336,7 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  			if (err)
>  				goto done;
>  			n++;
> -			if (base_file)
> +			if (base_buf == NULL)
>  				rewind(base_file);
>  			continue;
>  		}
> @@ -1359,6 +1358,50 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  				goto done;
>  			}
>  		}
> +
> +		err = got_delta_get_sizes(&base_size, &result_size,
> +		    delta_buf, delta_len);
> +		if (err)
> +			goto done;
> +		if (base_size > max_size)
> +			max_size = base_size;
> +		if (result_size > max_size)
> +			max_size = result_size;
> +
> +		if (base_buf && max_size > max_bufsize) {
> +			/* Switch from buffers to temporary files. */
> +			size_t w = fwrite(base_buf, 1, base_bufsz,
> +			    base_file);
> +			if (w != base_bufsz) {
> +				err = got_ferror(outfile, GOT_ERR_IO);
> +				goto done;
> +			}
> +			free(base_buf);
> +			base_buf = NULL;
> +			free(accum_buf);
> +			accum_buf = NULL;
> +		}
> +
> +		if (base_buf && max_size > base_bufsz) {
> +			uint8_t *p = realloc(base_buf, max_size);
> +			if (p == NULL) {
> +				err = got_error_from_errno("realloc");
> +				goto done;
> +			}
> +			base_buf = p;
> +			base_bufsz = max_size;
> +		}
> +
> +		if (accum_buf && max_size > accum_bufsz) {
> +			uint8_t *p = realloc(accum_buf, max_size);
> +			if (p == NULL) {
> +				err = got_error_from_errno("realloc");
> +				goto done;
> +			}
> +			accum_buf = p;
> +			accum_bufsz = max_size;
> +		}
> +
>  		if (base_buf) {
>  			err = got_delta_apply_in_mem(base_buf, base_bufsz,
>  			    delta_buf, delta_len, accum_buf,
> @@ -1380,25 +1423,11 @@ got_pack_dump_delta_chain_to_file(size_t *result_size,
>  			/* Accumulated delta becomes the new base. */
>  			if (base_buf) {
>  				uint8_t *tmp = accum_buf;
> -				/*
> -				 * Base buffer switches roles with accumulation
> -				 * buffer. Ensure it can hold the largest
> -				 * result in the delta chain. The initial
> -				 * allocation might have been smaller.
> -				 */
> -				if (base_bufsz < max_size) {
> -					uint8_t *p;
> -					p = reallocarray(base_buf, 1, max_size);
> -					if (p == NULL) {
> -						err = got_error_from_errno(
> -						    "reallocarray");
> -						goto done;
> -					}
> -					base_buf = p;
> -					base_bufsz = max_size;
> -				}
> +				size_t tmp_size = accum_bufsz;
>  				accum_buf = base_buf;
> +				accum_bufsz = base_bufsz;
>  				base_buf = tmp;
> +				base_bufsz = tmp_size;
>  			} else {
>  				FILE *tmp = accum_file;
>  				accum_file = base_file;
> @@ -1430,8 +1459,8 @@ got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, siz
>  	const struct got_error *err = NULL;
>  	struct got_delta *delta;
>  	uint8_t *base_buf = NULL, *accum_buf = NULL, *delta_buf;
> -	size_t base_bufsz = 0, accum_size = 0, delta_len;
> -	uint64_t max_size;
> +	size_t base_bufsz = 0, accum_bufsz = 0, accum_size = 0, delta_len;
> +	uint64_t max_size = 0;
>  	int n = 0;
>  
>  	*outbuf = NULL;
> @@ -1440,15 +1469,9 @@ got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, siz
>  	if (STAILQ_EMPTY(&deltas->entries))
>  		return got_error(GOT_ERR_BAD_DELTA_CHAIN);
>  
> -	err = got_pack_get_delta_chain_max_size(&max_size, deltas, pack);
> -	if (err)
> -		return err;
> -	accum_buf = malloc(max_size);
> -	if (accum_buf == NULL)
> -		return got_error_from_errno("malloc");
> -
>  	/* Deltas are ordered in ascending order. */
>  	STAILQ_FOREACH(delta, &deltas->entries, entry) {
> +		uint64_t base_size, result_size = 0;
>  		int cached = 1;
>  		if (n == 0) {
>  			size_t delta_data_offset;
> @@ -1467,6 +1490,10 @@ got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, siz
>  				err = got_error(GOT_ERR_PACK_OFFSET);
>  				goto done;
>  			}
> +
> +			if (delta->size > max_size)
> +				max_size = delta->size;
> +
>  			if (pack->map) {
>  				size_t mapoff = (size_t)delta_data_offset;
>  				err = got_inflate_to_mem_mmap(&base_buf,
> @@ -1505,6 +1532,36 @@ got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, siz
>  				goto done;
>  			}
>  		}
> +
> +		err = got_delta_get_sizes(&base_size, &result_size,
> +		    delta_buf, delta_len);
> +		if (err)
> +			goto done;
> +		if (base_size > max_size)
> +			max_size = base_size;
> +		if (result_size > max_size)
> +			max_size = result_size;
> +
> +		if (max_size > base_bufsz) {
> +			uint8_t *p = realloc(base_buf, max_size);
> +			if (p == NULL) {
> +				err = got_error_from_errno("realloc");
> +				goto done;
> +			}
> +			base_buf = p;
> +			base_bufsz = max_size;
> +		}
> +
> +		if (max_size > accum_bufsz) {
> +			uint8_t *p = realloc(accum_buf, max_size);
> +			if (p == NULL) {
> +				err = got_error_from_errno("realloc");
> +				goto done;
> +			}
> +			accum_buf = p;
> +			accum_bufsz = max_size;
> +		}
> +
>  		err = got_delta_apply_in_mem(base_buf, base_bufsz,
>  		    delta_buf, delta_len, accum_buf,
>  		    &accum_size, max_size);
> @@ -1517,24 +1574,11 @@ got_pack_dump_delta_chain_to_mem(uint8_t **outbuf, siz
>  		if (n < deltas->nentries) {
>  			/* Accumulated delta becomes the new base. */
>  			uint8_t *tmp = accum_buf;
> -			/*
> -			 * Base buffer switches roles with accumulation buffer.
> -			 * Ensure it can hold the largest result in the delta
> -			 * chain. Initial allocation might have been smaller.
> -			 */
> -			if (base_bufsz < max_size) {
> -				uint8_t *p;
> -				p = reallocarray(base_buf, 1, max_size);
> -				if (p == NULL) {
> -					err = got_error_from_errno(
> -					    "reallocarray");
> -					goto done;
> -				}
> -				base_buf = p;
> -				base_bufsz = max_size;
> -			}
> +			size_t tmp_size = accum_bufsz;
>  			accum_buf = base_buf;
> +			accum_bufsz = base_bufsz;
>  			base_buf = tmp;
> +			base_bufsz = tmp_size;
>  		}
>  	}
>  
> 
>