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

[PATCH v2 4/5] sideband: use writev(3p) to send pktlines

From
Patrick Steinhardt <ps@pks.im>
Date
Aug 7, 2026, 06:18 UTC
Message-ID
<20260807-pks-reintroduce-writev-v2-4-30fcff0e89c1@pks.im>
In-Reply-To
<20260807-pks-reintroduce-writev-v2-0-30fcff0e89c1@pks.im>

Every pktline that we send out via `send_sideband()` currently requires two syscalls: one to write the pktline's length, and one to send its data. This typically isn't all that much of a problem, but under extreme load the syscalls may cause contention in the kernel.

Refactor the code to instead use the newly introduced writev(3p) infra so that we can send out the data with a single syscall. This reduces the number of syscalls from around 133,000 calls to write(3p) to around 67,000 calls to writev(3p).

This change leads to a performance improvement for git-upload-pack(1), but we have to cheat a bit to really make it measurable. Usually, the time is strongly dominated by generating the packfile itself. But if we precompute the pack and serve it via the pack-objects hook then we can essentially eliminate that overhead. The following setup is executed in the Git repository:

  $ cat >request <<-EOF
  0048want 5ce91c059e41090e7d2cffad39c04af8acf98dc1 side-band no-progress
  00000009done
  EOF
  $ echo 5ce91c059e41090e7d2cffad39c04af8acf98dc1 | git pack-objects --revs --stdout >pack
  $ cat >hook <<-EOF
  #!/bin/sh
  cat >/dev/null
  cat "$(pwd)"/pack
  EOF
  $ chmod u+x hook
  $ git -c uploadpack.packObjectsHook="$(pwd)"/hook upload-pack . <request
Benchmarking the last command leads to the following results:
  Benchmark 1: HEAD~
    Time (mean ± σ):     192.9 ms ±   0.6 ms    [User: 106.5 ms, System: 95.3 ms]
    Range (min … max):   191.7 ms … 194.1 ms    50 runs
  Benchmark 2: HEAD
    Time (mean ± σ):     141.1 ms ±   0.7 ms    [User: 63.2 ms, System: 86.6 ms]
    Range (min … max):   139.8 ms … 142.7 ms    50 runs
  Summary
    HEAD ran
      1.37 ± 0.01 times faster than HEAD~

This might not be impressive in absolute numbers when you also take into account the time it takes to generate the packfile itself. But GitLab (and supposedly other forges) have caching mechanisms in place that work exactly like the above setup, where repeated incoming requests can be served from the same cached packfile. And in those cases, the impact is sizeable.

More importantly though, as hinted at above, GitLab has observed in the past that with enough cache hits we eventually start to saturate a semaphore in the Linux kernel itself in the pipe write path. This bottleneck is being moved a bit by having to do less syscalls.

Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 sideband.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/sideband.c b/sideband.c
index 1523a53e1d..94e5b56172 100644
--- a/sideband.c
+++ b/sideband.c
@@ -441,6 +441,7 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma
 	const char *p = data;
 
 	while (sz) {
+		struct iovec iov[2];
 		unsigned n;
 		char hdr[5];
 
@@ -450,12 +451,19 @@ 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;
+
+		writev_or_die(fd, iov, ARRAY_SIZE(iov));
+
 		p += n;
 		sz -= n;
 	}
-- 
2.55.0.679.g6767b8d81c.dirty
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 26 of 27 in “Reintroduce writev(3p)”
  1. 0/5 Reintroduce writev(3p)Patrick Steinhardt, Jul 16, 2026
  2. 1/5 compat/posix: introduce writev(3p) wrapperPatrick Steinhardt, Jul 16, 2026
  3. Simon RichterJul 16, 2026
  4. Junio C HamanoJul 16, 2026
  5. Junio C HamanoJul 16, 2026
  6. Patrick SteinhardtAug 5, 2026
  7. 2/5 wrapper: introduce writev(3p) wrappersPatrick Steinhardt, Jul 16, 2026
  8. 3/5 wrapper: properly handle MAX_IO_SIZE in writev(3p)Patrick Steinhardt, Jul 16, 2026
  9. 4/5 sideband: use writev(3p) to send pktlinesPatrick Steinhardt, Jul 16, 2026
  10. 5/5 fast-import: use writev(3p) to send cat-blob responsesPatrick Steinhardt, Jul 16, 2026
  11. Johannes SixtJul 16, 2026
  12. Junio C HamanoJul 27, 2026
  13. Patrick SteinhardtAug 5, 2026
  14. Junio C HamanoAug 5, 2026
  15. Johannes SixtAug 5, 2026
  16. Junio C HamanoAug 5, 2026
  17. Johannes SixtAug 5, 2026
  18. Junio C HamanoAug 5, 2026
  19. Patrick SteinhardtAug 6, 2026
  20. Junio C HamanoAug 6, 2026
  21. Patrick SteinhardtAug 7, 2026
  22. 0/5 Reintroduce writev(3p)Patrick Steinhardt, Aug 7, 2026
  23. 1/5 compat/posix: introduce writev(3p) wrapperPatrick Steinhardt, Aug 7, 2026
  24. 2/5 wrapper: introduce writev(3p) wrappersPatrick Steinhardt, Aug 7, 2026
  25. 3/5 wrapper: properly handle MAX_IO_SIZE in writev(3p)Patrick Steinhardt, Aug 7, 2026
  26. 4/5 sideband: use writev(3p) to send pktlinesPatrick Steinhardt, Aug 7, 2026
  27. 5/5 fast-import: use writev(3p) to send cat-blob responsesPatrick Steinhardt, Aug 7, 2026

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.