Another slash at index size

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I noticed that even with v4, we still duplicate a lot of info in the
remaining fields. ce_uid and ce_guid for instance are unlikely to
change ever between entries. So I attempt to store offsets between the
previous entry instead. The result looks good. This is webkit index:

 25M index-v2
 14M index-v4
7.7M index-v5
4.5M index-v5.gz

gzip beats me naturally, we still have a lot of spare bits and we
don't use dictionaries. But the code is simpler and should run faster
than gzip.

Performance is improved too:

$ time GIT_INDEX_FILE=index-v2 ./git ls-files |head -n1 >/dev/null

real    0m0.437s
user    0m0.385s
sys     0m0.048s
$ time GIT_INDEX_FILE=index-v4 ./git ls-files |head -n1 >/dev/null

real    0m0.319s
user    0m0.277s
sys     0m0.040s
$ time GIT_INDEX_FILE=index-v5 ./git ls-files |head -n1 >/dev/null

real    0m0.250s
user    0m0.213s
sys     0m0.036s

Some details on the new format:

 - in general varint is used to store numbers, unless we know the
   numbers are really big.
 - flags is the first field on disk, it has extra bits to let git know
   what to do with the rest of the fields
 - Many fields like ctime, mtime, uid, gid, dev, ino are stored as
   offsets
 - ce_mode's special values 100644 and 100755 are stored in flags. So
   unless you use gitlinks or something else, ce_mode will not be
   stored.
 - ce_namelen is no longer stored on disk
 - pathname is compressed just like in v4

---
 cache.h      |   2 +-
 read-cache.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 269 insertions(+), 6 deletions(-)

diff --git a/cache.h b/cache.h
index e493563..6ab53e7 100644
--- a/cache.h
+++ b/cache.h
@@ -106,7 +106,7 @@ struct cache_header {
 };
 
 #define INDEX_FORMAT_LB 2
-#define INDEX_FORMAT_UB 4
+#define INDEX_FORMAT_UB 5
 
 /*
  * The "cache_time" is just the low 32 bits of the
diff --git a/read-cache.c b/read-cache.c
index 827ae55..147ace1 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1260,7 +1260,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
 	if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
 		return error("bad signature");
 	hdr_version = ntohl(hdr->hdr_version);
-	if (hdr_version < 2 || 4 < hdr_version)
+	if (hdr_version < 2 || 5 < hdr_version)
 		return error("bad index version %d", hdr_version);
 	git_SHA1_Init(&c);
 	git_SHA1_Update(&c, hdr, size - 20);
@@ -1407,6 +1407,137 @@ static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
 	return ce;
 }
 
+/*
+ * Often used flags come first to keep flags in common case small so
+ * that encode_varint() produces fewer bytes
+ */
+#define CE5_OFS_MTIME		(1 << 0)
+#define CE5_MODEMASK		(3 << 1)
+#define CE5_MODE_644		 0
+#define CE5_MODE_755		(1 << 1)
+#define CE5_MODE_FULL		(2 << 1)
+#define CE5_FULL_INO		(1 << 3)
+#define CE5_STAGESHIFT		 4
+#define CE5_STAGEMASK		(3 << CE5_STAGESHIFT)
+#define CE5_ITA			(1 << 6)
+#define CE5_VALID		(1 << 7)
+#define CE5_SWT			(1 << 8)
+#define CE5_FULL_UID		(1 << 9)
+#define CE5_FULL_GID		(1 << 10)
+#define CE5_FULL_TIME		(1 << 11)
+#define CE5_FULL_DEV		(1 << 12)
+
+static uintmax_t decode_offset(const unsigned char **bufp, uintmax_t base)
+{
+	uintmax_t offset = decode_varint(bufp);
+	if (offset & 1)	/* negative */
+		return base - (offset >> 1);
+	else
+		return base + (offset >> 1);
+
+}
+
+static struct cache_entry *create_from_disk_v5(const unsigned char *data,
+					       unsigned long *consumed,
+					       struct strbuf *previous_name,
+					       const struct cache_entry *previous_ce)
+{
+	const unsigned char *orig = data;
+	struct cache_entry ce_tmp;
+	struct cache_entry *ce;
+	unsigned int flags;
+
+	flags = decode_varint(&data);
+	ce_tmp.ce_flags = ((flags & CE5_STAGEMASK) >> CE5_STAGESHIFT) << CE_STAGESHIFT;
+	if (flags & CE5_ITA)
+		ce_tmp.ce_flags |= CE_INTENT_TO_ADD;
+	if (flags & CE5_VALID)
+		ce_tmp.ce_flags |= CE_VALID;
+	if (flags & CE5_SWT)
+		ce_tmp.ce_flags |= CE_SKIP_WORKTREE;
+
+	if (flags & CE5_FULL_TIME) { /* full format, 16 bytes */
+		ce_tmp.ce_ctime.sec = ntoh_l(*(uint32_t*)data);
+		data += sizeof(uint32_t);
+		ce_tmp.ce_ctime.nsec = ntoh_l(*(uint32_t*)data);
+		data += sizeof(uint32_t);
+
+		ce_tmp.ce_mtime.sec = ntoh_l(*(uint32_t*)data);
+		data += sizeof(uint32_t);
+		ce_tmp.ce_mtime.nsec = ntoh_l(*(uint32_t*)data);
+		data += sizeof(uint32_t);
+	} else {
+		/* offset from previous ce */
+		ce_tmp.ce_ctime.sec = decode_offset(&data, previous_ce->ce_ctime.sec);
+		ce_tmp.ce_ctime.nsec = decode_offset(&data, previous_ce->ce_ctime.nsec);
+
+		if (flags & CE5_OFS_MTIME) {
+			/* offset from previous ce */
+			ce_tmp.ce_mtime.sec = decode_offset(&data, previous_ce->ce_mtime.sec);
+			ce_tmp.ce_mtime.nsec = decode_offset(&data, previous_ce->ce_mtime.nsec);
+		} else
+			ce_tmp.ce_mtime = ce_tmp.ce_ctime;
+	}
+
+
+	if (flags & CE5_FULL_DEV)
+		ce_tmp.ce_dev = decode_varint(&data);
+	else
+		ce_tmp.ce_dev = previous_ce->ce_dev;
+	if (flags & CE5_FULL_INO) {
+		ce_tmp.ce_ino = ntoh_l(*(uint32_t*)data);
+		data += sizeof(uint32_t);
+	} else {
+		uintmax_t offset = decode_varint(&data);
+		if (offset & 1)	/* negative */
+			ce_tmp.ce_ino = previous_ce->ce_ino - (offset >> 1);
+		else
+			ce_tmp.ce_ino = previous_ce->ce_ino + (offset >> 1);
+	}
+	switch (flags & CE5_MODEMASK) {
+	case CE5_MODE_644:
+		ce_tmp.ce_mode = 0100644;
+		break;
+	case CE5_MODE_755:
+		ce_tmp.ce_mode = 0100755;
+		break;
+	case CE5_MODE_FULL:
+		ce_tmp.ce_mode = decode_varint(&data);
+		break;
+	default:
+		die("Huh?");
+	}
+	if (flags & CE5_FULL_UID)
+		ce_tmp.ce_uid = decode_varint(&data);
+	else
+		ce_tmp.ce_uid = previous_ce->ce_uid;
+	if (flags & CE5_FULL_GID)
+		ce_tmp.ce_gid = decode_varint(&data);
+	else
+		ce_tmp.ce_gid = previous_ce->ce_gid;
+	ce_tmp.ce_size = decode_varint(&data);
+	hashcpy(ce_tmp.sha1, data);
+	data += 20;
+
+	*consumed = data - orig;
+	*consumed += expand_name_field(previous_name, (const char *)data);
+
+	ce = xmalloc(cache_entry_size(previous_name->len));
+	ce->ce_ctime = ce_tmp.ce_ctime;
+	ce->ce_mtime = ce_tmp.ce_mtime;
+	ce->ce_dev   = ce_tmp.ce_dev;
+	ce->ce_ino   = ce_tmp.ce_ino;
+	ce->ce_mode  = ce_tmp.ce_mode;
+	ce->ce_uid   = ce_tmp.ce_uid;
+	ce->ce_gid   = ce_tmp.ce_gid;
+	ce->ce_size  = ce_tmp.ce_size;
+	ce->ce_flags = ce_tmp.ce_flags;
+	ce->ce_namelen = previous_name->len;
+	hashcpy(ce->sha1, ce_tmp.sha1);
+	memcpy(ce->name, previous_name->buf, previous_name->len + 1);
+	return ce;
+}
+
 /* remember to discard_cache() before reading a different cache! */
 int read_index_from(struct index_state *istate, const char *path)
 {
@@ -1452,7 +1583,7 @@ int read_index_from(struct index_state *istate, const char *path)
 	istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
 	istate->initialized = 1;
 
-	if (istate->version == 4)
+	if (istate->version >= 4)
 		previous_name = &previous_name_buf;
 	else
 		previous_name = NULL;
@@ -1464,7 +1595,13 @@ int read_index_from(struct index_state *istate, const char *path)
 		unsigned long consumed;
 
 		disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
-		ce = create_from_disk(disk_ce, &consumed, previous_name);
+		if (istate->version == 5)
+			ce = create_from_disk_v5((const unsigned char*)disk_ce,
+						 &consumed,
+						 previous_name,
+						 i > 0 ? istate->cache[i - 1] : NULL);
+		else
+			ce = create_from_disk(disk_ce, &consumed, previous_name);
 		set_index_entry(istate, i, ce);
 
 		src_offset += consumed;
@@ -1652,6 +1789,125 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
 	}
 }
 
+static void strbuf_encode_varint(struct strbuf *sb, uintmax_t value)
+{
+	static unsigned char varint[16];
+	int varint_len = encode_varint(value, varint);
+	strbuf_add(sb, varint, varint_len);
+}
+
+static void strbuf_encode_offset(struct strbuf *sb, unsigned int v1, unsigned int v2)
+{
+	if (v1 < v2)
+		strbuf_encode_varint(sb, (v2 - v1) << 1);
+	else
+		strbuf_encode_varint(sb, 1 | ((v1 - v2) << 1));
+}
+
+static int ce_write_entry_v5(git_SHA_CTX *c, int fd,
+			     struct cache_entry *ce,
+			     struct strbuf *previous_name,
+			     struct cache_entry *previous_ce)
+{
+	uint32_t data[4];
+	unsigned int flags = 0;
+	struct strbuf sb = STRBUF_INIT;
+	int result;
+
+	if (ce->ce_flags & CE_INTENT_TO_ADD)
+		flags |= CE5_ITA;
+	if (ce->ce_flags & CE_VALID)
+		flags |= CE5_VALID;
+	if (ce->ce_flags & CE_SKIP_WORKTREE)
+		flags |= CE5_SWT;
+	flags |= ce_stage(ce) << CE5_STAGESHIFT;
+
+	if (previous_ce) {
+		if (ce->ce_ctime.sec != ce->ce_mtime.sec ||
+		    ce->ce_ctime.nsec != ce->ce_mtime.nsec)
+			flags |= CE5_OFS_MTIME;
+		if (ce->ce_uid != previous_ce->ce_uid)
+			flags |= CE5_FULL_UID;
+		if (ce->ce_gid != previous_ce->ce_gid)
+			flags |= CE5_FULL_GID;
+		if (ce->ce_dev != previous_ce->ce_dev)
+			flags |= CE5_FULL_DEV;
+	} else
+		flags |= CE5_FULL_TIME | CE5_FULL_UID | CE5_FULL_GID | CE5_FULL_DEV;
+	if (ce->ce_mode == 0100644)
+		flags |= CE5_MODE_644; /* no bit sets actually */
+	else if (ce->ce_mode == 0100755)
+		flags |= CE5_MODE_755;
+	else
+		flags |= CE5_MODE_FULL;
+	if (previous_ce &&
+	    (ce->ce_ino - previous_ce->ce_ino < 16000 ||
+	     previous_ce->ce_ino - ce->ce_ino < 16000))
+		;		/* inode offset */
+	else
+		flags |= CE5_FULL_INO;
+
+	strbuf_encode_varint(&sb, flags);
+
+	if (flags & CE5_FULL_TIME) { /* full format, 16 bytes */
+		data[0] = htonl(ce->ce_ctime.sec);
+		data[1] = htonl(ce->ce_ctime.nsec);
+		data[2] = htonl(ce->ce_mtime.sec);
+		data[3] = htonl(ce->ce_mtime.nsec);
+		strbuf_add(&sb, data, sizeof(data));
+	} else {
+		/* offset from previous ce */
+		strbuf_encode_offset(&sb, previous_ce->ce_ctime.sec, ce->ce_ctime.sec);
+		strbuf_encode_offset(&sb, previous_ce->ce_ctime.nsec, ce->ce_ctime.nsec);
+
+		if (flags & CE5_OFS_MTIME) {
+			/* offset from previous ce */
+			strbuf_encode_offset(&sb, previous_ce->ce_mtime.sec, ce->ce_mtime.sec);
+			strbuf_encode_offset(&sb, previous_ce->ce_mtime.nsec, ce->ce_mtime.nsec);
+		}
+	}
+
+	if (flags & CE5_FULL_DEV)
+		strbuf_encode_varint(&sb, ce->ce_dev);
+	if (flags & CE5_FULL_INO) {
+		data[0] =  htonl(ce->ce_ino);
+		strbuf_add(&sb, data, sizeof(*data));
+	} else
+		strbuf_encode_offset(&sb, previous_ce->ce_ino, ce->ce_ino);
+	if (flags & CE5_MODE_FULL)
+		strbuf_encode_varint(&sb, ce->ce_mode);
+	if (flags & CE5_FULL_UID)
+		strbuf_encode_varint(&sb, ce->ce_uid);
+	if (flags & CE5_FULL_GID)
+		strbuf_encode_varint(&sb, ce->ce_gid);
+	strbuf_encode_varint(&sb, ce->ce_size);
+	strbuf_add(&sb, ce->sha1, 20);
+
+	if (previous_name) {
+		int common, to_remove, prefix_size;
+		unsigned char to_remove_vi[16];
+		for (common = 0;
+		     (ce->name[common] &&
+		      common < previous_name->len &&
+		      ce->name[common] == previous_name->buf[common]);
+		     common++)
+			; /* still matching */
+		to_remove = previous_name->len - common;
+		prefix_size = encode_varint(to_remove, to_remove_vi);
+
+		strbuf_add(&sb, to_remove_vi, prefix_size);
+		strbuf_add(&sb, ce->name + common, ce_namelen(ce) - common);
+
+		strbuf_splice(previous_name, common, to_remove,
+			      ce->name + common, ce_namelen(ce) - common);
+	} else
+		strbuf_add(&sb, ce->name, ce_namelen(ce));
+
+	result = ce_write(c, fd, sb.buf, sb.len + 1);
+	strbuf_release(&sb);
+	return result;
+}
+
 /* Copy miscellaneous fields but not the name */
 static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
 				       struct cache_entry *ce)
@@ -1793,16 +2049,23 @@ int write_index(struct index_state *istate, int newfd)
 	if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
 		return -1;
 
-	previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
+	previous_name = (hdr_version >= 4) ? &previous_name_buf : NULL;
 	for (i = 0; i < entries; i++) {
 		struct cache_entry *ce = cache[i];
+		int ret;
 		if (ce->ce_flags & CE_REMOVE)
 			continue;
 		if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
 			ce_smudge_racily_clean_entry(ce);
 		if (is_null_sha1(ce->sha1))
 			return error("cache entry has null sha1: %s", ce->name);
-		if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
+		if (hdr_version == 5)
+			ret = ce_write_entry_v5(&c, newfd, ce,
+						previous_name,
+						i > 0 ? cache[i-1] : NULL);
+		else
+			ret = ce_write_entry(&c, newfd, ce, previous_name);
+		if (ret < 0)
 			return -1;
 	}
 	strbuf_release(&previous_name_buf);
-- 
1.8.1.2.495.g3fdf5d5.dirty

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]