All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/1] Make upload-pack pack write size configurable
@ 2021-12-13 13:23 Jacob Vosmaer
  2021-12-13 13:23 ` [PATCH 1/1] upload-pack.c: make output buffer " Jacob Vosmaer
  2021-12-14 15:12 ` [PATCH 0/1] Make upload-pack pack write " Jeff King
  0 siblings, 2 replies; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-13 13:23 UTC (permalink / raw)
  To: git; +Cc: Jacob Vosmaer

When transfering packfile data, upload-pack.c uses an 8KB buffer.
This is a reasonable size but when you transfer a lot of packfile
data, like we do on GitLab.com, we find it is beneficial to use a
larger buffer size.

Below you will find a commit where we make the size of this 8KB
buffer configurable at compile time. It appears pack-objects always
does 8KB writes so I don't think we should change the default. But
for GitLab, where we have a cache for the output of pack-objects,
it is beneficial to use a larger IO size because the cache does
64KB writes.

I have also considered converting the packfile copying code to use
stdio when writing to stdout, but that would be a bigger change
because we have to be careful not to interleave stdio and stdlib
writes. And we would have to make the stdout output buffer size
configurable, because the default stdio buffer size is 4KB which
is no better than the status quo. A final argument against the stdio
approach is that it only reduces the number of writes from upload-pack,
while a larger buffer size reduces both the number of reads and
writes.

Having said all that, if the Git maintainers prefer the stdio
approach over this compile time constant, I am happy to submit a
patch series for that instead.

Thanks,

Jacob Vosmaer

Jacob Vosmaer (1):
  upload-pack.c: make output buffer size configurable

 upload-pack.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

-- 
2.33.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 1/1] upload-pack.c: make output buffer size configurable
  2021-12-13 13:23 [PATCH 0/1] Make upload-pack pack write size configurable Jacob Vosmaer
@ 2021-12-13 13:23 ` Jacob Vosmaer
  2021-12-14 12:08   ` Ævar Arnfjörð Bjarmason
  2021-12-14 15:37   ` [PATCH 1/1] upload-pack.c: make output buffer size configurable Jeff King
  2021-12-14 15:12 ` [PATCH 0/1] Make upload-pack pack write " Jeff King
  1 sibling, 2 replies; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-13 13:23 UTC (permalink / raw)
  To: git; +Cc: Jacob Vosmaer

Add a new compile time constant UPLOAD_PACK_BUFFER_SIZE that makes the
size of the static buffer in output_state configurable.

The current size of the buffer is 8192+1. The '+1' is a technical
detail orthogonal to this change. On GitLab.com we use GitLab's
pack-objects cache which does writes of 65515 bytes. Because of the
default 8KB buffer size, propagating these cache writes requires 8
pipe reads and 8 pipe writes from git-upload-pack, and 8 pipe reads
from Gitaly (our Git RPC service). If we increase the size of the
buffer to the maximum Git packet size, we need only 1 pipe read and 1
pipe write in git-upload-pack, and 1 pipe read in Gitaly to transfer
the same amount of data. In benchmarks with a pure fetch and 100%
cache hit rate workload we are seeing CPU utilization reductions of
over 30%.

Signed-off-by: Jacob Vosmaer <jacob@gitlab.com>
---
 upload-pack.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/upload-pack.c b/upload-pack.c
index c78d55bc67..b799fbe628 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -42,6 +42,10 @@
 #define ALL_FLAGS (THEY_HAVE | OUR_REF | WANTED | COMMON_KNOWN | SHALLOW | \
 		NOT_SHALLOW | CLIENT_SHALLOW | HIDDEN_REF)
 
+#ifndef UPLOAD_PACK_BUFFER_SIZE
+#define UPLOAD_PACK_BUFFER_SIZE 8192
+#endif
+
 /* Enum for allowed unadvertised object request (UOR) */
 enum allow_uor {
 	/* Allow specifying sha1 if it is a ref tip. */
@@ -194,7 +198,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 }
 
 struct output_state {
-	char buffer[8193];
+	char buffer[UPLOAD_PACK_BUFFER_SIZE+1];
 	int used;
 	unsigned packfile_uris_started : 1;
 	unsigned packfile_started : 1;
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/1] upload-pack.c: make output buffer size configurable
  2021-12-13 13:23 ` [PATCH 1/1] upload-pack.c: make output buffer " Jacob Vosmaer
@ 2021-12-14 12:08   ` Ævar Arnfjörð Bjarmason
  2021-12-14 15:08     ` Jeff King
  2021-12-14 15:37   ` [PATCH 1/1] upload-pack.c: make output buffer size configurable Jeff King
  1 sibling, 1 reply; 17+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-14 12:08 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git


On Mon, Dec 13 2021, Jacob Vosmaer wrote:

> Add a new compile time constant UPLOAD_PACK_BUFFER_SIZE that makes the
> size of the static buffer in output_state configurable.
>
> The current size of the buffer is 8192+1. The '+1' is a technical
> detail orthogonal to this change. On GitLab.com we use GitLab's
> pack-objects cache which does writes of 65515 bytes. Because of the
> default 8KB buffer size, propagating these cache writes requires 8
> pipe reads and 8 pipe writes from git-upload-pack, and 8 pipe reads
> from Gitaly (our Git RPC service). If we increase the size of the
> buffer to the maximum Git packet size, we need only 1 pipe read and 1
> pipe write in git-upload-pack, and 1 pipe read in Gitaly to transfer
> the same amount of data. In benchmarks with a pure fetch and 100%
> cache hit rate workload we are seeing CPU utilization reductions of
> over 30%.
>
> Signed-off-by: Jacob Vosmaer <jacob@gitlab.com>
> ---
>  upload-pack.c | 6 +++++-
>  1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/upload-pack.c b/upload-pack.c
> index c78d55bc67..b799fbe628 100644
> --- a/upload-pack.c
> +++ b/upload-pack.c
> @@ -42,6 +42,10 @@
>  #define ALL_FLAGS (THEY_HAVE | OUR_REF | WANTED | COMMON_KNOWN | SHALLOW | \
>  		NOT_SHALLOW | CLIENT_SHALLOW | HIDDEN_REF)
>  
> +#ifndef UPLOAD_PACK_BUFFER_SIZE
> +#define UPLOAD_PACK_BUFFER_SIZE 8192
> +#endif
> +
>  /* Enum for allowed unadvertised object request (UOR) */
>  enum allow_uor {
>  	/* Allow specifying sha1 if it is a ref tip. */
> @@ -194,7 +198,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
>  }
>  
>  struct output_state {
> -	char buffer[8193];
> +	char buffer[UPLOAD_PACK_BUFFER_SIZE+1];
>  	int used;
>  	unsigned packfile_uris_started : 1;
>  	unsigned packfile_started : 1;

Making this configurable obviousl has big impact in some cases, but I'm
a bit iffy on the faciltity to do so + it not being documented.

I don't think that the "static buffer" part here is important to anyone,
but the write() size is clearly important.

So doesn't it make more sense to have a uploadPack.bufferSize=8k
variable we can tweak, just make this "buffer" a "struct strbuf" instead
(i.e. it'll by dynamically grown), and then just flush it whenever we
hit the configured buffer size?

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/1] upload-pack.c: make output buffer size configurable
  2021-12-14 12:08   ` Ævar Arnfjörð Bjarmason
@ 2021-12-14 15:08     ` Jeff King
  2021-12-14 19:46       ` [PATCH v2 0/1] upload-pack.c: increase output buffer size Jacob Vosmaer
  0 siblings, 1 reply; 17+ messages in thread
From: Jeff King @ 2021-12-14 15:08 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Jacob Vosmaer, git

On Tue, Dec 14, 2021 at 01:08:55PM +0100, Ævar Arnfjörð Bjarmason wrote:

> > +#ifndef UPLOAD_PACK_BUFFER_SIZE
> > +#define UPLOAD_PACK_BUFFER_SIZE 8192
> > +#endif
> > +
> >  /* Enum for allowed unadvertised object request (UOR) */
> >  enum allow_uor {
> >  	/* Allow specifying sha1 if it is a ref tip. */
> > @@ -194,7 +198,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
> >  }
> >  
> >  struct output_state {
> > -	char buffer[8193];
> > +	char buffer[UPLOAD_PACK_BUFFER_SIZE+1];
> >  	int used;
> >  	unsigned packfile_uris_started : 1;
> >  	unsigned packfile_started : 1;
> 
> Making this configurable obviousl has big impact in some cases, but I'm
> a bit iffy on the faciltity to do so + it not being documented.
> 
> I don't think that the "static buffer" part here is important to anyone,
> but the write() size is clearly important.
> 
> So doesn't it make more sense to have a uploadPack.bufferSize=8k
> variable we can tweak, just make this "buffer" a "struct strbuf" instead
> (i.e. it'll by dynamically grown), and then just flush it whenever we
> hit the configured buffer size?

I don't think we want to grow dynamically, because we don't want to hold
arbitrary amounts of data in memory. We're just passing it through. But
if there were a run-time config option, we could easily heap-allocate
the buffer up front.

That may be overkill, though I do agree this is kind of weirdly
undocumented. There are two other subtleties I notice:

  - This output_state struct does go on the stack, so something big like
    64k is questionable there (though on Linux, without recursion, it's
    usually OK).

  - we're relaying the data into pkt-lines. Do we run into problems when
    the buffer is larger than a packet? I think in send_sideband() we'll
    break it up as appropriate. But before we hit the PACK header, we
    send out packfile-uris directly with packet_write_fmt(). Those
    aren't likely to be long, but if they are, we'd die() in
    format_packet(). So something around LARGE_PACKET_DATA_MAX is
    probably the highest you'd want to set it anyway (and the most
    performant, since otherwise you have to write out extra partial
    packets).

So I kind of wonder if there is any real _harm_ in just always using
bigger packets, even if it does not always help. Given the subtle rules
about packet-max above, then we could just use that optimal value and
not worry about configurability.

-Peff

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 0/1] Make upload-pack pack write size configurable
  2021-12-13 13:23 [PATCH 0/1] Make upload-pack pack write size configurable Jacob Vosmaer
  2021-12-13 13:23 ` [PATCH 1/1] upload-pack.c: make output buffer " Jacob Vosmaer
@ 2021-12-14 15:12 ` Jeff King
  1 sibling, 0 replies; 17+ messages in thread
From: Jeff King @ 2021-12-14 15:12 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git

On Mon, Dec 13, 2021 at 02:23:44PM +0100, Jacob Vosmaer wrote:

> When transfering packfile data, upload-pack.c uses an 8KB buffer.
> This is a reasonable size but when you transfer a lot of packfile
> data, like we do on GitLab.com, we find it is beneficial to use a
> larger buffer size.
> 
> Below you will find a commit where we make the size of this 8KB
> buffer configurable at compile time. It appears pack-objects always
> does 8KB writes so I don't think we should change the default. But
> for GitLab, where we have a cache for the output of pack-objects,
> it is beneficial to use a larger IO size because the cache does
> 64KB writes.

I suspect the big reason that it matters for the cache is that for
pack-objects, we're typically CPU bound computing the sha1 of the
outgoing pack (for its trailer).

I do suspect we could just always move to something closer to
LARGE_PACKET_DATA_MAX (probably minus 1 for the sideband marker). Even
if pack-objects only writes in 8k chunks, we may be able to pull several
in one read(), especially if the network is a bottleneck (because we'd
block writing to the client, and pack-objects would fill up the pipe
buffer).

I.e., it doesn't seem like there's any real downside to doing so.

> I have also considered converting the packfile copying code to use
> stdio when writing to stdout, but that would be a bigger change
> because we have to be careful not to interleave stdio and stdlib
> writes. And we would have to make the stdout output buffer size
> configurable, because the default stdio buffer size is 4KB which
> is no better than the status quo. A final argument against the stdio
> approach is that it only reduces the number of writes from upload-pack,
> while a larger buffer size reduces both the number of reads and
> writes.

Yeah, I don't think that would help all that much. We really want to
size this based on pkt-line limits. That reduces syscalls, but also
shrinks the overall size a little (since larger packets minimizes the
framing overhead of the pkt-line headers).

-Peff

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/1] upload-pack.c: make output buffer size configurable
  2021-12-13 13:23 ` [PATCH 1/1] upload-pack.c: make output buffer " Jacob Vosmaer
  2021-12-14 12:08   ` Ævar Arnfjörð Bjarmason
@ 2021-12-14 15:37   ` Jeff King
  2021-12-14 20:04     ` Jacob Vosmaer
  1 sibling, 1 reply; 17+ messages in thread
From: Jeff King @ 2021-12-14 15:37 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git

On Mon, Dec 13, 2021 at 02:23:45PM +0100, Jacob Vosmaer wrote:

> The current size of the buffer is 8192+1. The '+1' is a technical
> detail orthogonal to this change. On GitLab.com we use GitLab's
> pack-objects cache which does writes of 65515 bytes. Because of the
> default 8KB buffer size, propagating these cache writes requires 8
> pipe reads and 8 pipe writes from git-upload-pack, and 8 pipe reads
> from Gitaly (our Git RPC service). If we increase the size of the
> buffer to the maximum Git packet size, we need only 1 pipe read and 1
> pipe write in git-upload-pack, and 1 pipe read in Gitaly to transfer
> the same amount of data. In benchmarks with a pure fetch and 100%
> cache hit rate workload we are seeing CPU utilization reductions of
> over 30%.

I was curious to reproduce this locally, so I came up with:

  (
    printf "003fwant %s side-band-64k" $(git rev-parse HEAD)
    printf 0000
    echo '0009done'
  ) |
  git -c uploadpack.packobjectsHook='cat objects/pack/pack-*.pack; :' upload-pack . |
  pv >/dev/null

which hackily simulates the server side of your "cached" case. :) I ran
it on a fully-packed clone of linux.git.

It gets about 2.3GB/s with the tip of 'master' and 3.2GB/s with the
equivalent of your patch (using LARGE_PACKET_DATA_MAX). So definitely an
improvement.

Without the cached case (so actually running pack-objects, albeit a
pretty quick one because of bitmaps and pack-reuse), the timings are
about the same (171MB/s versus 174MB/s, but really it's just pegging a
CPU running pack-objects). So it would be fine to just do this
unconditionally, I think.

Looking at strace, the other thing I notice is that we write() the
packet header separately in send_sideband(), which doubles the number of
syscalls. I hackily re-wrote this to use writev() instead (patch below),
but it doesn't seem to actually help much (maybe a curiosity to explore
further, but definitely not something to hold up your patch).

-Peff

---
diff --git a/sideband.c b/sideband.c
index 85bddfdcd4..d0945507a3 100644
--- a/sideband.c
+++ b/sideband.c
@@ -5,6 +5,11 @@
 #include "help.h"
 #include "pkt-line.h"
 
+/* hack; should go in git-compat-util, and should provide compat
+ * wrapper around regular write()
+ */
+#include <sys/uio.h>
+
 struct keyword_entry {
 	/*
 	 * We use keyword as config key so it should be a single alphanumeric word.
@@ -257,6 +262,7 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma
 
 	while (sz) {
 		unsigned n;
+		struct iovec iov[2];
 		char hdr[5];
 
 		n = sz;
@@ -265,12 +271,17 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma
 		if (0 <= band) {
 			xsnprintf(hdr, sizeof(hdr), "%04x", n + 5);
 			hdr[4] = band;
-			write_or_die(fd, hdr, 5);
+			iov[0].iov_base = hdr;
+			iov[0].iov_len = 5;
 		} else {
 			xsnprintf(hdr, sizeof(hdr), "%04x", n + 4);
-			write_or_die(fd, hdr, 4);
+			iov[0].iov_base = hdr;
+			iov[0].iov_len = 4;
 		}
-		write_or_die(fd, p, n);
+		iov[1].iov_base = (void *)p;
+		iov[1].iov_len = n;
+		/* should check for errors, but also short writes and EINTR, etc */
+		writev(fd, iov, 2);
 		p += n;
 		sz -= n;
 	}
diff --git a/upload-pack.c b/upload-pack.c
index c78d55bc67..111de8c60c 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -194,7 +194,7 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 }
 
 struct output_state {
-	char buffer[8193];
+	char buffer[LARGE_PACKET_DATA_MAX];
 	int used;
 	unsigned packfile_uris_started : 1;
 	unsigned packfile_started : 1;

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 0/1] upload-pack.c: increase output buffer size
  2021-12-14 15:08     ` Jeff King
@ 2021-12-14 19:46       ` Jacob Vosmaer
  2021-12-14 19:46         ` [PATCH v2 1/1] " Jacob Vosmaer
  0 siblings, 1 reply; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-14 19:46 UTC (permalink / raw)
  To: git, avarab, peff; +Cc: Jacob Vosmaer

Thank you Ævar and Peff for your comments on the previous iteration
of this patch. I have made the buffer size fixed at the pktline
maximum, and I have changed the storage of output_state from stack
to heap.

As far as I can tell, all return paths from create_pack_file call
"die", except the happy path. So I only free the heap-allocated
output_state on the happy path. Is that OK, or should I use a "goto
out" function shape?

Jacob Vosmaer (1):
  upload-pack.c: increase output buffer size

 upload-pack.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

-- 
2.33.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-14 19:46       ` [PATCH v2 0/1] upload-pack.c: increase output buffer size Jacob Vosmaer
@ 2021-12-14 19:46         ` Jacob Vosmaer
  2021-12-14 20:41           ` Ævar Arnfjörð Bjarmason
                             ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-14 19:46 UTC (permalink / raw)
  To: git, avarab, peff; +Cc: Jacob Vosmaer

When serving a fetch, git upload-pack copies data from a git
pack-objects stdout pipe to its stdout. This commit increases the size
of the buffer used for that copying from 8192 to 65515, the maximum
sideband-64k packet size.

Previously, this buffer was allocated on the stack. Because the new
buffer size is nearly 64KB, we switch this to a heap allocation.

On GitLab.com we use GitLab's pack-objects cache which does writes of
65515 bytes. Because of the default 8KB buffer size, propagating these
cache writes requires 8 pipe reads and 8 pipe writes from
git-upload-pack, and 8 pipe reads from Gitaly (our Git RPC service).
If we increase the size of the buffer to the maximum Git packet size,
we need only 1 pipe read and 1 pipe write in git-upload-pack, and 1
pipe read in Gitaly to transfer the same amount of data. In benchmarks
with a pure fetch and 100% cache hit rate workload we are seeing CPU
utilization reductions of over 30%.

Signed-off-by: Jacob Vosmaer <jacob@gitlab.com>
---
 upload-pack.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/upload-pack.c b/upload-pack.c
index c78d55bc67..3b90fb69e6 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -194,7 +194,13 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 }
 
 struct output_state {
-	char buffer[8193];
+	/*
+	 * We do writes no bigger than LARGE_PACKET_DATA_MAX - 1, because with
+	 * sideband-64k the band designator takes up 1 byte of space. Because
+	 * relay_pack_data keeps the last byte to itself, we make the buffer 1
+	 * byte bigger than the intended maximum write size.
+	 */
+	char buffer[(LARGE_PACKET_DATA_MAX - 1) + 1];
 	int used;
 	unsigned packfile_uris_started : 1;
 	unsigned packfile_started : 1;
@@ -269,7 +275,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
 			     const struct string_list *uri_protocols)
 {
 	struct child_process pack_objects = CHILD_PROCESS_INIT;
-	struct output_state output_state = { { 0 } };
+	struct output_state *output_state = xcalloc(1, sizeof(struct output_state));
 	char progress[128];
 	char abort_msg[] = "aborting due to possible repository "
 		"corruption on the remote side.";
@@ -404,7 +410,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
 		}
 		if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
 			int result = relay_pack_data(pack_objects.out,
-						     &output_state,
+						     output_state,
 						     pack_data->use_sideband,
 						     !!uri_protocols);
 
@@ -438,11 +444,12 @@ static void create_pack_file(struct upload_pack_data *pack_data,
 	}
 
 	/* flush the data */
-	if (output_state.used > 0) {
-		send_client_data(1, output_state.buffer, output_state.used,
+	if (output_state->used > 0) {
+		send_client_data(1, output_state->buffer, output_state->used,
 				 pack_data->use_sideband);
 		fprintf(stderr, "flushed.\n");
 	}
+	free(output_state);
 	if (pack_data->use_sideband)
 		packet_flush(1);
 	return;
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/1] upload-pack.c: make output buffer size configurable
  2021-12-14 15:37   ` [PATCH 1/1] upload-pack.c: make output buffer size configurable Jeff King
@ 2021-12-14 20:04     ` Jacob Vosmaer
  0 siblings, 0 replies; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-14 20:04 UTC (permalink / raw)
  To: Jeff King; +Cc: git

On Tue, Dec 14, 2021 at 4:37 PM Jeff King <peff@peff.net> wrote:
>
> It gets about 2.3GB/s with the tip of 'master' and 3.2GB/s with the
> equivalent of your patch (using LARGE_PACKET_DATA_MAX). So definitely an
> improvement.
>
> Without the cached case (so actually running pack-objects, albeit a
> pretty quick one because of bitmaps and pack-reuse), the timings are
> about the same (171MB/s versus 174MB/s, but really it's just pegging a
> CPU running pack-objects). So it would be fine to just do this
> unconditionally, I think.

Thank you for validating this!

> Looking at strace, the other thing I notice is that we write() the
> packet header separately in send_sideband(), which doubles the number of
> syscalls. I hackily re-wrote this to use writev() instead (patch below),
> but it doesn't seem to actually help much (maybe a curiosity to explore
> further, but definitely not something to hold up your patch).

Those double writes bug me too. Getting rid of them was one of the
things I liked about using stdio here. Curious now if writev will make
an impact in my benchmarks. As your experiments indicate, the double
writes may not be a problem in real life. Either way, I also feel it
is not something to rope into this patch set.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-14 19:46         ` [PATCH v2 1/1] " Jacob Vosmaer
@ 2021-12-14 20:41           ` Ævar Arnfjörð Bjarmason
  2021-12-15 16:30           ` Jeff King
  2021-12-15 19:50           ` Junio C Hamano
  2 siblings, 0 replies; 17+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-14 20:41 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git, peff


On Tue, Dec 14 2021, Jacob Vosmaer wrote:

> When serving a fetch, git upload-pack copies data from a git
> pack-objects stdout pipe to its stdout. This commit increases the size
> of the buffer used for that copying from 8192 to 65515, the maximum
> sideband-64k packet size.
>
> Previously, this buffer was allocated on the stack. Because the new
> buffer size is nearly 64KB, we switch this to a heap allocation.
>
> On GitLab.com we use GitLab's pack-objects cache which does writes of
> 65515 bytes. Because of the default 8KB buffer size, propagating these
> cache writes requires 8 pipe reads and 8 pipe writes from
> git-upload-pack, and 8 pipe reads from Gitaly (our Git RPC service).
> If we increase the size of the buffer to the maximum Git packet size,
> we need only 1 pipe read and 1 pipe write in git-upload-pack, and 1
> pipe read in Gitaly to transfer the same amount of data. In benchmarks
> with a pure fetch and 100% cache hit rate workload we are seeing CPU
> utilization reductions of over 30%.
>
> Signed-off-by: Jacob Vosmaer <jacob@gitlab.com>
> ---
>  upload-pack.c | 17 ++++++++++++-----
>  1 file changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/upload-pack.c b/upload-pack.c
> index c78d55bc67..3b90fb69e6 100644
> --- a/upload-pack.c
> +++ b/upload-pack.c
> @@ -194,7 +194,13 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
>  }
>  
>  struct output_state {
> -	char buffer[8193];
> +	/*
> +	 * We do writes no bigger than LARGE_PACKET_DATA_MAX - 1, because with
> +	 * sideband-64k the band designator takes up 1 byte of space. Because
> +	 * relay_pack_data keeps the last byte to itself, we make the buffer 1
> +	 * byte bigger than the intended maximum write size.
> +	 */
> +	char buffer[(LARGE_PACKET_DATA_MAX - 1) + 1];

Since X-1+1 = X shouldn't we just say X ? :)

Maybe this fixup is better, maybe not:
	
	diff --git a/upload-pack.c b/upload-pack.c
	index 3b90fb69e6d..10849110229 100644
	--- a/upload-pack.c
	+++ b/upload-pack.c
	@@ -195,12 +195,12 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
	 
	 struct output_state {
	 	/*
	-	 * We do writes no bigger than LARGE_PACKET_DATA_MAX - 1, because with
	-	 * sideband-64k the band designator takes up 1 byte of space. Because
	-	 * relay_pack_data keeps the last byte to itself, we make the buffer 1
	-	 * byte bigger than the intended maximum write size.
	+	 * We do writes no bigger than LARGE_PACKET_DATA_MAX - 1,
	+	 * because with * sideband-64k the band designator takes up 1
	+	 * byte of space (see relay_pack_data() below). So
	+	 * LARGE_PACKET_DATA_MAX ends up being the right size.
	 	 */
	-	char buffer[(LARGE_PACKET_DATA_MAX - 1) + 1];
	+	char buffer[LARGE_PACKET_DATA_MAX];
	 	int used;
	 	unsigned packfile_uris_started : 1;
	 	unsigned packfile_started : 1;

>  	int used;
>  	unsigned packfile_uris_started : 1;
>  	unsigned packfile_started : 1;
> @@ -269,7 +275,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
>  			     const struct string_list *uri_protocols)
>  {
>  	struct child_process pack_objects = CHILD_PROCESS_INIT;
> -	struct output_state output_state = { { 0 } };
> +	struct output_state *output_state = xcalloc(1, sizeof(struct output_state));

I don't know when/if we need to worry about 8k v.s. ~65k on the stack,
especially as recv_sideband() has:

	char buf[LARGE_PACKET_MAX + 1];

But maybe our stack is already quite big here, I don't know...

>  	char progress[128];
>  	char abort_msg[] = "aborting due to possible repository "
>  		"corruption on the remote side.";
> @@ -404,7 +410,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
>  		}
>  		if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
>  			int result = relay_pack_data(pack_objects.out,
> -						     &output_state,
> +						     output_state,
>  						     pack_data->use_sideband,
>  						     !!uri_protocols);
>  
> @@ -438,11 +444,12 @@ static void create_pack_file(struct upload_pack_data *pack_data,
>  	}
>  
>  	/* flush the data */
> -	if (output_state.used > 0) {
> -		send_client_data(1, output_state.buffer, output_state.used,
> +	if (output_state->used > 0) {
> +		send_client_data(1, output_state->buffer, output_state->used,
>  				 pack_data->use_sideband);
>  		fprintf(stderr, "flushed.\n");
>  	}
> +	free(output_state);
>  	if (pack_data->use_sideband)
>  		packet_flush(1);
>  	return;

But this looks fine either way. And yes, in reply to the question in the
cover letter it's fine to ignore the memory leaks we have when we call
die().

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-14 19:46         ` [PATCH v2 1/1] " Jacob Vosmaer
  2021-12-14 20:41           ` Ævar Arnfjörð Bjarmason
@ 2021-12-15 16:30           ` Jeff King
  2021-12-15 19:50           ` Junio C Hamano
  2 siblings, 0 replies; 17+ messages in thread
From: Jeff King @ 2021-12-15 16:30 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git, avarab

On Tue, Dec 14, 2021 at 08:46:26PM +0100, Jacob Vosmaer wrote:

> When serving a fetch, git upload-pack copies data from a git
> pack-objects stdout pipe to its stdout. This commit increases the size
> of the buffer used for that copying from 8192 to 65515, the maximum
> sideband-64k packet size.
> 
> Previously, this buffer was allocated on the stack. Because the new
> buffer size is nearly 64KB, we switch this to a heap allocation.
> 
> On GitLab.com we use GitLab's pack-objects cache which does writes of
> 65515 bytes. Because of the default 8KB buffer size, propagating these
> cache writes requires 8 pipe reads and 8 pipe writes from
> git-upload-pack, and 8 pipe reads from Gitaly (our Git RPC service).
> If we increase the size of the buffer to the maximum Git packet size,
> we need only 1 pipe read and 1 pipe write in git-upload-pack, and 1
> pipe read in Gitaly to transfer the same amount of data. In benchmarks
> with a pure fetch and 100% cache hit rate workload we are seeing CPU
> utilization reductions of over 30%.

Thanks, this patch looks good to me.

>  struct output_state {
> -	char buffer[8193];
> +	/*
> +	 * We do writes no bigger than LARGE_PACKET_DATA_MAX - 1, because with
> +	 * sideband-64k the band designator takes up 1 byte of space. Because
> +	 * relay_pack_data keeps the last byte to itself, we make the buffer 1
> +	 * byte bigger than the intended maximum write size.
> +	 */
> +	char buffer[(LARGE_PACKET_DATA_MAX - 1) + 1];

I don't have an opinion on the "-1 / +1" thing mentioned elsewhere. What
I care much more about is that the comment explains what is going on,
and what you wrote here is easy to understand.

> @@ -269,7 +275,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
>  			     const struct string_list *uri_protocols)
>  {
>  	struct child_process pack_objects = CHILD_PROCESS_INIT;
> -	struct output_state output_state = { { 0 } };
> +	struct output_state *output_state = xcalloc(1, sizeof(struct output_state));

I think this hunk is worth doing. As Ævar noted, we do sometimes have
pretty big stack variables elsewhere, so we might have been able to get
away with it here.  But running out of stack can be unpredictable and
annoying. Given how easy and low-cost it is to put it on the heap here,
I'm glad to just do it preemptively.

> @@ -438,11 +444,12 @@ static void create_pack_file(struct upload_pack_data *pack_data,
>  	}
>  
>  	/* flush the data */
> -	if (output_state.used > 0) {
> -		send_client_data(1, output_state.buffer, output_state.used,
> +	if (output_state->used > 0) {
> +		send_client_data(1, output_state->buffer, output_state->used,
>  				 pack_data->use_sideband);
>  		fprintf(stderr, "flushed.\n");
>  	}
> +	free(output_state);
>  	if (pack_data->use_sideband)
>  		packet_flush(1);
>  	return;

There's a "fail:" label just below here. But since it does not return,
and just calls die(), then I agree you don't need to worry about freeing
in that case (and there are no other returns from the function). So this
single free is sufficient.

-Peff

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-14 19:46         ` [PATCH v2 1/1] " Jacob Vosmaer
  2021-12-14 20:41           ` Ævar Arnfjörð Bjarmason
  2021-12-15 16:30           ` Jeff King
@ 2021-12-15 19:50           ` Junio C Hamano
  2021-12-15 19:59             ` rsbecker
  2 siblings, 1 reply; 17+ messages in thread
From: Junio C Hamano @ 2021-12-15 19:50 UTC (permalink / raw)
  To: Jacob Vosmaer; +Cc: git, avarab, peff

Jacob Vosmaer <jacob@gitlab.com> writes:

> When serving a fetch, git upload-pack copies data from a git
> pack-objects stdout pipe to its stdout. This commit increases the size
> of the buffer used for that copying from 8192 to 65515, the maximum
> sideband-64k packet size.

Thanks.  I agree with others that extra configurability is not
needed in this case, and allocating this on the heap (as long as we
correctly deallocate it when we are done) is the right thing.

Will queue.  Thanks.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-15 19:50           ` Junio C Hamano
@ 2021-12-15 19:59             ` rsbecker
  2021-12-15 20:24               ` Jacob Vosmaer
  0 siblings, 1 reply; 17+ messages in thread
From: rsbecker @ 2021-12-15 19:59 UTC (permalink / raw)
  To: 'Junio C Hamano', 'Jacob Vosmaer'; +Cc: git, avarab, peff

On December 15, 2021 2:51 PM, Junio C Hamano wrote:
> Jacob Vosmaer <jacob@gitlab.com> writes:
> 
> > When serving a fetch, git upload-pack copies data from a git
> > pack-objects stdout pipe to its stdout. This commit increases the size
> > of the buffer used for that copying from 8192 to 65515, the maximum
> > sideband-64k packet size.
> 
> Thanks.  I agree with others that extra configurability is not needed in
this
> case, and allocating this on the heap (as long as we correctly deallocate
it
> when we are done) is the right thing.

This is likely to break NonStop as our sideband packet size is less than
64K.
-Randall


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-15 19:59             ` rsbecker
@ 2021-12-15 20:24               ` Jacob Vosmaer
  2021-12-15 20:38                 ` rsbecker
  0 siblings, 1 reply; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-15 20:24 UTC (permalink / raw)
  To: rsbecker; +Cc: Junio C Hamano, git, avarab, peff

On Wed, Dec 15, 2021 at 8:59 PM <rsbecker@nexbridge.com> wrote:

> This is likely to break NonStop as our sideband packet size is less than
> 64K.

Could you elaborate on what you expect to break?

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-15 20:24               ` Jacob Vosmaer
@ 2021-12-15 20:38                 ` rsbecker
  2021-12-15 20:45                   ` Jacob Vosmaer
  0 siblings, 1 reply; 17+ messages in thread
From: rsbecker @ 2021-12-15 20:38 UTC (permalink / raw)
  To: 'Jacob Vosmaer'; +Cc: 'Junio C Hamano', git, avarab, peff

On December 15, 2021 3:24 PM, Jacob Vosmaer wrote:
> To: rsbecker@nexbridge.com
> Cc: Junio C Hamano <gitster@pobox.com>; git@vger.kernel.org;
> avarab@gmail.com; peff@peff.net
> Subject: Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
> 
> On Wed, Dec 15, 2021 at 8:59 PM <rsbecker@nexbridge.com> wrote:
> 
> > This is likely to break NonStop as our sideband packet size is less
> > than 64K.
> 
> Could you elaborate on what you expect to break?

The maximum I/O size on the platform is 56Kb. Anything larger will fail. That's why we use xread and xwrite for non-buffered I/O.
-Randall


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-15 20:38                 ` rsbecker
@ 2021-12-15 20:45                   ` Jacob Vosmaer
  2021-12-15 21:34                     ` rsbecker
  0 siblings, 1 reply; 17+ messages in thread
From: Jacob Vosmaer @ 2021-12-15 20:45 UTC (permalink / raw)
  To: rsbecker; +Cc: Junio C Hamano, git, avarab, peff

On Wed, Dec 15, 2021 at 9:38 PM <rsbecker@nexbridge.com> wrote:
>
> The maximum I/O size on the platform is 56Kb. Anything larger will fail. That's why we use xread and xwrite for non-buffered I/O.

That sounds like it's orthogonal to what this patch is changing.
Regardless of this patch, the actual writes are performed by
write_or_die, which calls write_in_full. In turn, write_in_full calls
xwrite however often is necessary. We are not making any assumptions
anywhere about how large individual write syscalls turn out. Git
packets use userspace buffers so we can read and write them no matter
what the underlying IO sizes are.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [PATCH v2 1/1] upload-pack.c: increase output buffer size
  2021-12-15 20:45                   ` Jacob Vosmaer
@ 2021-12-15 21:34                     ` rsbecker
  0 siblings, 0 replies; 17+ messages in thread
From: rsbecker @ 2021-12-15 21:34 UTC (permalink / raw)
  To: 'Jacob Vosmaer'; +Cc: 'Junio C Hamano', git, avarab, peff

On December 15, 2021 3:45 PM, Jacob Vosmaer wrote:
> On Wed, Dec 15, 2021 at 9:38 PM <rsbecker@nexbridge.com> wrote:
> >
> > The maximum I/O size on the platform is 56Kb. Anything larger will fail.
> That's why we use xread and xwrite for non-buffered I/O.
> 
> That sounds like it's orthogonal to what this patch is changing.
> Regardless of this patch, the actual writes are performed by write_or_die,
> which calls write_in_full. In turn, write_in_full calls xwrite however often is
> necessary. We are not making any assumptions anywhere about how large
> individual write syscalls turn out. Git packets use userspace buffers so we can
> read and write them no matter what the underlying IO sizes are.

In that case, it works for me. xread/xwrite know how to deal with the situation. Thanks for the clarification.
-Randall


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2021-12-15 21:35 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-12-13 13:23 [PATCH 0/1] Make upload-pack pack write size configurable Jacob Vosmaer
2021-12-13 13:23 ` [PATCH 1/1] upload-pack.c: make output buffer " Jacob Vosmaer
2021-12-14 12:08   ` Ævar Arnfjörð Bjarmason
2021-12-14 15:08     ` Jeff King
2021-12-14 19:46       ` [PATCH v2 0/1] upload-pack.c: increase output buffer size Jacob Vosmaer
2021-12-14 19:46         ` [PATCH v2 1/1] " Jacob Vosmaer
2021-12-14 20:41           ` Ævar Arnfjörð Bjarmason
2021-12-15 16:30           ` Jeff King
2021-12-15 19:50           ` Junio C Hamano
2021-12-15 19:59             ` rsbecker
2021-12-15 20:24               ` Jacob Vosmaer
2021-12-15 20:38                 ` rsbecker
2021-12-15 20:45                   ` Jacob Vosmaer
2021-12-15 21:34                     ` rsbecker
2021-12-14 15:37   ` [PATCH 1/1] upload-pack.c: make output buffer size configurable Jeff King
2021-12-14 20:04     ` Jacob Vosmaer
2021-12-14 15:12 ` [PATCH 0/1] Make upload-pack pack write " Jeff King

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.