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

packv4: current performance

From
Nicolas Pitre <nico@fluxnic.net>
Date
Sep 9, 2013, 19:14 UTC
Message-ID
<alpine.LFD.2.03.1309091454450.20709@syhkavp.arg>

Of course pack v4, while significantly better on disk space, is still lagging behind pack v2 in terms of runtime performance. This is because right now the reading side is implemented as a compatibility layer that parses the packv4 data to regenerate a canonical object representation, which is then parsed again by the core code. The full runtime benefit of pack v4 will be apparent when the core code is optimized to use pack v4 data directly (hint hint to potential contributors).

Still, I've looked a bit closer at the performance issue, and one thing was clear: stdio string functions are very costly. So I've added the patch below to my tree which provided a big boost in performance.

    packv4-parse.c: get rid of snprintf()
    
    Profiling 'git rev-list --all --objects' shows snprintf() related
    functions topping the total execution time chart.
    
    Let's replace that with specialized string generation functions.
    This reduced the total runtime by a wopping 40% on a Core2 processor.
    
    Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
diff --git a/packv4-parse.c b/packv4-parse.c
index 63bba03..c62c4ae 100644
--- a/packv4-parse.c
+++ b/packv4-parse.c
@@ -163,6 +163,77 @@ const unsigned char *get_pathref(struct packed_git *p, unsigned int index)
 	return p->path_dict->data + p->path_dict->offsets[index];
 }
 
+static int tree_line(unsigned char *buf, unsigned long size,
+		     const char *label, int label_len,
+		     const unsigned char *sha1)
+{
+	static const char hex[] = "0123456789abcdef";
+	int i;
+
+	if (label_len + 1 + 40 + 1 > size)
+		return 0;
+
+	memcpy(buf, label, label_len);
+	buf += label_len;
+	*buf++ = ' ';
+
+	for (i = 0; i < 20; i++) {
+		unsigned int val = *sha1++;
+		*buf++ = hex[val >> 4];
+		*buf++ = hex[val & 0xf];
+	}
+
+	*buf = '\n';
+
+	return label_len + 1 + 40 + 1;
+}
+
+static int ident_line(unsigned char *buf, unsigned long size,
+		      const char *label, int label_len,
+		      const unsigned char *ident, unsigned long time, int tz)
+{
+	int ident_len = strlen((const char *)ident);
+	int len = label_len + 1 + ident_len + 1 + 1 + 5 + 1;
+	int time_len = 0;
+	unsigned char time_buf[16];
+
+	do {
+		time_buf[time_len++] = '0' + time % 10;
+		time /= 10;
+	} while (time);
+	len += time_len;
+
+	if (len > size)
+		return 0;
+
+	memcpy(buf, label, label_len);
+	buf += label_len;
+	*buf++ = ' ';
+
+	memcpy(buf, ident, ident_len);
+	buf += ident_len;
+	*buf++ = ' ';
+
+	do {
+		*buf++ = time_buf[--time_len];
+	} while (time_len);
+	*buf++ = ' ';
+
+	if (tz < 0) {
+		tz = -tz;
+		*buf++ = '-';
+	} else
+		*buf++ = '+';
+	*buf++ = '0' + tz / 1000; tz %= 1000;
+	*buf++ = '0' + tz / 100;  tz %= 100;
+	*buf++ = '0' + tz / 10;   tz %= 10;
+	*buf++ = '0' + tz;
+
+	*buf = '\n';
+
+	return len;
+}
+
 void *pv4_get_commit(struct packed_git *p, struct pack_window **w_curs,
 		     off_t offset, unsigned long size)
 {
@@ -182,15 +253,17 @@ void *pv4_get_commit(struct packed_git *p, struct pack_window **w_curs,
 	scp = src;
 
 	sha1 = get_sha1ref(p, &scp);
-	len = snprintf((char *)dcp, size, "tree %s\n", sha1_to_hex(sha1));
+	len = tree_line(dcp, size, "tree", strlen("tree"), sha1);
+	if (!len)
+		die("overflow in %s", __func__);
 	dcp += len;
 	size -= len;
 
 	nb_parents = decode_varint(&scp);
 	while (nb_parents--) {
 		sha1 = get_sha1ref(p, &scp);
-		len = snprintf((char *)dcp, size, "parent %s\n", sha1_to_hex(sha1));
-		if (len >= size)
+		len = tree_line(dcp, size, "parent", strlen("parent"), sha1);
+		if (!len)
 			die("overflow in %s", __func__);
 		dcp += len;
 		size -= len;
@@ -211,16 +284,16 @@ void *pv4_get_commit(struct packed_git *p, struct pack_window **w_curs,
 	else
 		author_time = commit_time - (author_time >> 1);
 
-	len = snprintf((char *)dcp, size, "author %s %lu %+05d\n",
-			author, author_time, author_tz);
-	if (len >= size)
+	len = ident_line(dcp, size, "author", strlen("author"), 
+			 author, author_time, author_tz);
+	if (!len)
 		die("overflow in %s", __func__);
 	dcp += len;
 	size -= len;
 
-	len = snprintf((char *)dcp, size, "committer %s %lu %+05d\n",
-			committer, commit_time, commit_tz);
-	if (len >= size)
+	len = ident_line(dcp, size, "committer", strlen("committer"),
+			 committer, commit_time, commit_tz);
+	if (!len)
 		die("overflow in %s", __func__);
 	dcp += len;
 	size -= len;
@@ -248,6 +321,32 @@ void *pv4_get_commit(struct packed_git *p, struct pack_window **w_curs,
 	return dst;
 }
 
+static int tree_entry_prefix(unsigned char *buf, unsigned long size,
+			     const unsigned char *path, unsigned mode)
+{
+	int path_len = strlen((const char *)path) + 1;
+	int mode_len = 0;
+	int len;
+	unsigned char mode_buf[8];
+
+	do {
+		mode_buf[mode_len++] = '0' + (mode & 7);
+		mode >>= 3;
+	} while (mode);
+
+	len = mode_len + 1 + path_len;
+	if (len > size)
+		return 0;
+
+	do {
+		*buf++ = mode_buf[--mode_len];
+	} while (mode_len);
+	*buf++ = ' ';
+	memcpy(buf, path, path_len);
+
+	return len;
+}
+
 static int decode_entries(struct packed_git *p, struct pack_window **w_curs,
 			  off_t offset, unsigned int start, unsigned int count,
 			  unsigned char **dstp, unsigned long *sizep, int hdr)
@@ -315,9 +414,8 @@ static int decode_entries(struct packed_git *p, struct pack_window **w_curs,
 			if (!path || !sha1)
 				return -1;
 			mode = (path[0] << 8) | path[1];
-			len = snprintf((char *)*dstp, *sizep, "%o %s%c",
-					   mode, path+2, '\0');
-			if (len + 20 > *sizep)
+			len = tree_entry_prefix(*dstp, *sizep, path + 2, mode);
+			if (!len || len + 20 > *sizep)
 				return -1;
 			hashcpy(*dstp + len, sha1);
 			*dstp += len + 20;
Message 1 of 1 in “packv4: current performance”
  1. Nicolas PitreSep 9, 2013

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.