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

Re: [PATCH v2 10/10] cat-file: use writev(2) if available

From
Junio C Hamano <gitster@pobox.com>
Date
Aug 27, 2024, 05:41 UTC
Message-ID
<xmqq1q2acyjo.fsf@gitster.g>
In-Reply-To
<20240823224630.1180772-11-e@80x24.org>
Eric Wong <e@80x24.org> writes:
Show 10 quoted lines
> diff --git a/builtin/cat-file.c b/builtin/cat-file.c
> index bf81054662..016b7d26a7 100644
> --- a/builtin/cat-file.c
> +++ b/builtin/cat-file.c
> @@ -280,7 +280,7 @@ struct expand_data {
>  	off_t disk_size;
>  	const char *rest;
>  	struct object_id delta_base_oid;
> -	void *content;
> +	struct git_iovec iov[3];

The earlier content pointer hinted that the caller that obtained data into this structure from the object layer can use it for any purpose that suits it, but using git_iovec structure screams that "we are going to write this thing out!". As "expand_data" is about what we are going to write out from cat-file anyway, is probably OK.

Having said that ...
Show 24 quoted lines
> -static void print_object_or_die(struct batch_options *opt, struct expand_data *data)
> +static void batch_writev(struct batch_options *opt, struct expand_data *data,
> +			const struct strbuf *hdr, size_t size)
> +{
> +	data->iov[0].iov_base = hdr->buf;
> +	data->iov[0].iov_len = hdr->len;
> +	data->iov[1].iov_len = size;
> +
> +	/*
> +	 * Copying a (8|16)-byte iovec for a single byte is gross, but my
> +	 * attempt to stuff output_delim into the trailing NUL byte of
> +	 * iov[1].iov_base (and restoring it after writev(2) for the
> +	 * OI_DBCACHED case) to drop iovcnt from 3->2 wasn't faster.
> +	 */
> +	data->iov[2].iov_base = &opt->output_delim;
> +	data->iov[2].iov_len = 1;
> +	if (opt->buffer_output)
> +		fwritev_or_die(stdout, data->iov, 3);
> +	else
> +		writev_or_die(1, data->iov, 3);
> +
> +	/* writev_or_die may move iov[1].iov_base, so it's invalid */
> +	data->iov[1].iov_base = NULL;
> +}

... the above made me read it twice, wondering "where does iov[1].iov_base comes from???" The location of the git_iovec structure in the expand_data forces this rather unnatural calling convention where the iovec is passed by address (as part of the expand_data structure), with only one of six slots filled, and the other five slots are filled by this function from the parameters passed to it.

I wonder if we can rework the data structure to
 - Not embed git_iovec iov[] in expand_data;
 - Keep "void *content" instead there;
 - Define an on-stack "struct git_iovec iov[3]" local to this function;
 - Pass "void *content" from the caller to this function;
 - Populate iov[] fully from hdr->{buf,len}, content, size, and
   opt->output_delim and consume it in this function by either
   calling fwritev_or_die() or writev_or_die().

That way, the caller does not have to use data->iov[1].iov_base in place of data->content, which is the source of "Huh? Why is the 2nd element of the 3-element array so special?" puzzlement readers would feel while reading the caller---after all, the fact that we are using writev with three chunks is an implementation detail that the caller does not have to know to correctly use this helper function.

Or am I missing something?
Show 30 quoted lines
> +static void print_object_or_die(struct batch_options *opt,
> +				struct expand_data *data, struct strbuf *hdr)
>  {
>  	const struct object_id *oid = &data->oid;
>  
>  	assert(data->info.typep);
>  
> -	if (data->content) {
> -		void *content = data->content;
> +	if (data->iov[1].iov_base) {
> +		void *content = data->iov[1].iov_base;
>  		unsigned long size = data->size;
>  
> -		data->content = NULL;
>  		if (use_mailmap && (data->type == OBJ_COMMIT ||
>  					data->type == OBJ_TAG)) {
>  			size_t s = size;
> @@ -399,10 +424,10 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
>  			}
>  
>  			content = replace_idents_using_mailmap(content, &s);
> +			data->iov[1].iov_base = content;
>  			size = cast_size_t_to_ulong(s);
>  		}
> -
> -		batch_write(opt, content, size);
> +		batch_writev(opt, data, hdr, size);
>  		switch (data->info.whence) {
>  		case OI_CACHED:
>  			/*

And with the "let's make iov[3] a local implementation detail of batch_writev()" approach, the above two hunks would shrink and essentialy we'd replace batch_write() with batch_writev() (with adjusted parameters).

Show 6 quoted lines
> @@ -419,8 +444,6 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
>  		}
>  	} else {
>  		assert(data->type == OBJ_BLOB);
> -		if (opt->buffer_output)
> -			fflush(stdout);

We used to fflush whatever we have written before entering this "else" clause. We no longer do so

Show 10 quoted lines
>  		if (opt->transform_mode) {
>  			char *contents;
>  			unsigned long size;
> @@ -447,10 +470,15 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
>  					    oid_to_hex(oid), data->rest);
>  			} else
>  				BUG("invalid transform_mode: %c", opt->transform_mode);
> -			batch_write(opt, contents, size);
> +			data->iov[1].iov_base = contents;
> +			batch_writev(opt, data, hdr, size);

And in the buffer_output mode, batch_writev() ends up calling fwritev_or_die(), which is merely a series of fwrite() calls. And the removed fflush() earlier is perfectly fine, as it was solely because we wanted to make sure fflush() before going down to direct file descriptor access with write(2) and we are now still going through stdio layer.

Show 5 quoted lines
>  			free(contents);
>  		} else {
> +			batch_write(opt, hdr->buf, hdr->len);
> +			if (opt->buffer_output)
> +				fflush(stdout);

The bigger else clause is entered with potentially unflushed bytes in the stdio buffer, as that was why we first fflush(). Then we do batch_write() here, which uses fwrite() in the buffer_output mode, without having to fflush(). But before doing stream_blob() below, we do need to fflush(). Makes sense.

Show 19 quoted lines
>  static void batch_one_object(const char *obj_name,
> @@ -666,7 +692,7 @@ static void parse_cmd_contents(struct batch_options *opt,
>  			     struct expand_data *data)
>  {
>  	opt->batch_mode = BATCH_MODE_CONTENTS;
> -	data->info.contentp = &data->content;
> +	data->info.contentp = &data->iov[1].iov_base;
>  	batch_one_object(line, output, opt, data);
>  }
>  
> @@ -823,7 +849,7 @@ static int batch_objects(struct batch_options *opt)
>  		data.info.typep = &data.type;
>  		if (!opt->transform_mode) {
>  			data.info.sizep = &data.size;
> -			data.info.contentp = &data.content;
> +			data.info.contentp = &data.iov[1].iov_base;
>  			data.info.content_limit = big_file_threshold;
>  			data.info.direct_cache = 1;
>  		}

If we do the "let's not leak the iov[3] implementation detail from batch_writev()" update, the above two hunks can be eliminated.

Show 17 quoted lines
> diff --git a/git-compat-util.h b/git-compat-util.h
> index ca7678a379..afde8abc99 100644
> --- a/git-compat-util.h
> +++ b/git-compat-util.h
> @@ -388,6 +388,16 @@ static inline int git_setitimer(int which UNUSED,
>  #define setitimer(which,value,ovalue) git_setitimer(which,value,ovalue)
>  #endif
>  
> +#ifdef HAVE_WRITEV
> +#include <sys/uio.h>
> +#define git_iovec iovec
> +#else /* !HAVE_WRITEV */
> +struct git_iovec {
> +	void *iov_base;
> +	size_t iov_len;
> +};
> +#endif /* !HAVE_WRITEV */
OK.
Show 20 quoted lines
> diff --git a/write-or-die.c b/write-or-die.c
> index 01a9a51fa2..227b051165 100644
> --- a/write-or-die.c
> +++ b/write-or-die.c
> @@ -107,3 +107,69 @@ void fflush_or_die(FILE *f)
>  	if (fflush(f))
>  		die_errno("fflush error");
>  }
> +
> +void fwritev_or_die(FILE *fp, const struct git_iovec *iov, int iovcnt)
> +{
> +	int i;
> +
> +	for (i = 0; i < iovcnt; i++) {
> +		size_t n = iov[i].iov_len;
> +
> +		if (fwrite(iov[i].iov_base, 1, n, fp) != n)
> +			die_errno("unable to write to FD=%d", fileno(fp));
> +	}
> +}
OK.
Previous: Eric WongNext: Junio C Hamano
Message 50 of 51 in “cat-file speedups”
  1. 00/10 cat-file speedupsEric Wong, Jul 15, 2024
  2. 01/10 packfile: move sizep computationEric Wong, Jul 15, 2024
  3. Patrick SteinhardtJul 24, 2024
  4. 02/10 packfile: allow content-limit for cat-fileEric Wong, Jul 15, 2024
  5. Patrick SteinhardtJul 24, 2024
  6. Eric WongJul 26, 2024
  7. 03/10 packfile: fix off-by-one in content_limit comparisonEric Wong, Jul 15, 2024
  8. Patrick SteinhardtJul 24, 2024
  9. Eric WongJul 26, 2024
  10. 04/10 packfile: inline cache_or_unpack_entryEric Wong, Jul 15, 2024
  11. 05/10 cat-file: use delta_base_cache entries directlyEric Wong, Jul 15, 2024
  12. Patrick SteinhardtJul 24, 2024
  13. Eric WongJul 26, 2024
  14. assert vs BUG [was: [PATCH v1 05/10] cat-file: use delta_base_cache entries directly]Eric Wong, Aug 18, 2024
  15. Junio C HamanoAug 19, 2024
  16. 06/10 packfile: packed_object_info avoids packed_to_object_typeEric Wong, Jul 15, 2024
  17. Patrick SteinhardtJul 24, 2024
  18. Eric WongJul 26, 2024
  19. 07/10 object_info: content_limit only applies to blobsEric Wong, Jul 15, 2024
  20. 08/10 cat-file: batch-command uses content_limitEric Wong, Jul 15, 2024
  21. 09/10 cat-file: batch_write: use size_t for lengthEric Wong, Jul 15, 2024
  22. 10/10 cat-file: use writev(2) if availableEric Wong, Jul 15, 2024
  23. Patrick SteinhardtJul 24, 2024
  24. 00/10 cat-file speedupsEric Wong, Aug 23, 2024
  25. 01/10 packfile: move sizep computationEric Wong, Aug 23, 2024
  26. Taylor BlauSep 17, 2024
  27. 02/10 packfile: allow content-limit for cat-fileEric Wong, Aug 23, 2024
  28. Junio C HamanoAug 26, 2024
  29. Eric WongAug 27, 2024
  30. Taylor BlauSep 17, 2024
  31. Junio C HamanoSep 17, 2024
  32. 03/10 packfile: fix off-by-one in content_limit comparisonEric Wong, Aug 23, 2024
  33. Junio C HamanoAug 26, 2024
  34. Taylor BlauSep 17, 2024
  35. 04/10 packfile: inline cache_or_unpack_entryEric Wong, Aug 23, 2024
  36. Junio C HamanoAug 26, 2024
  37. Eric WongOct 6, 2024
  38. 05/10 cat-file: use delta_base_cache entries directlyEric Wong, Aug 23, 2024
  39. Junio C HamanoAug 26, 2024
  40. Junio C HamanoAug 26, 2024
  41. 06/10 packfile: packed_object_info avoids packed_to_object_typeEric Wong, Aug 23, 2024
  42. Junio C HamanoAug 26, 2024
  43. 07/10 object_info: content_limit only applies to blobsEric Wong, Aug 23, 2024
  44. Junio C HamanoAug 26, 2024
  45. 08/10 cat-file: batch-command uses content_limitEric Wong, Aug 23, 2024
  46. Junio C HamanoAug 26, 2024
  47. 09/10 cat-file: batch_write: use size_t for lengthEric Wong, Aug 23, 2024
  48. Junio C HamanoAug 27, 2024
  49. 10/10 cat-file: use writev(2) if availableEric Wong, Aug 23, 2024
  50. Junio C HamanoAug 27, 2024
  51. Junio C HamanoAug 27, 2024

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.