read-cache: avoid allocating every ondisk entry when writing

When writing the index for each entry an ondisk struct will be allocated and freed in ce_write_entry. We can do better by using a ondisk struct on the stack for each entry. This is accomplished by using a stack ondisk_cache_entry_extended outside looping through the entries in do_write_index. Only the fixed fields of this struct are used when writing and depending on whether it is extended or not the flags2 field will be written. The name field is not used and instead the cache_entry name field is used directly when writing out the name. Because ce_write is using a buffer and memcpy to fill the buffer before flushing to disk, we don't have to worry about doing multiple ce_write calls. Running the p0007-write-cache.sh tests would save anywhere between 3-7% when the index had over a million entries with no performance degradation on small repos. Signed-off-by: Kevin Willford <kewillf@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kevin Willford committed Aug 21, 2017 at 15:24 UTC ce012deb98650b5b7bba58d534fed27628e34234
1 file changed +25 -25
read-cache.c
+25 -25
@@ -1499,6 +1499,7 @@ struct ondisk_cache_entry_extended {
1499 };
1500
1501 /* These are only used for v3 or lower */
1502 +#define align_padding_size(size, len) ((size + (len) + 8) & ~7) - (size + len)
1503 #define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
1504 #define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
1505 #define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
@@ -2032,7 +2033,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
2033 }
2034
2035 /* Copy miscellaneous fields but not the name */
2035 -static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
2036 +static void copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
2037 struct cache_entry *ce)
2038 {
2039 short flags;
@@ -2056,32 +2057,35 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
2057 struct ondisk_cache_entry_extended *ondisk2;
2058 ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
2059 ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
2059 - return ondisk2->name;
2060 - }
2061 - else {
2062 - return ondisk->name;
2060 }
2061 }
2062
2063 static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
2067 - struct strbuf *previous_name)
2064 + struct strbuf *previous_name, struct ondisk_cache_entry *ondisk)
2065 {
2066 int size;
2070 - struct ondisk_cache_entry *ondisk;
2067 int saved_namelen = saved_namelen; /* compiler workaround */
2072 - char *name;
2068 int result;
2069 + static unsigned char padding[8] = { 0x00 };
2070
2071 if (ce->ce_flags & CE_STRIP_NAME) {
2072 saved_namelen = ce_namelen(ce);
2073 ce->ce_namelen = 0;
2074 }
2075
2076 + if (ce->ce_flags & CE_EXTENDED)
2077 + size = offsetof(struct ondisk_cache_entry_extended, name);
2078 + else
2079 + size = offsetof(struct ondisk_cache_entry, name);
2080 +
2081 if (!previous_name) {
2081 - size = ondisk_ce_size(ce);
2082 - ondisk = xcalloc(1, size);
2083 - name = copy_cache_entry_to_ondisk(ondisk, ce);
2084 - memcpy(name, ce->name, ce_namelen(ce));
2082 + int len = ce_namelen(ce);
2083 + copy_cache_entry_to_ondisk(ondisk, ce);
2084 + result = ce_write(c, fd, ondisk, size);
2085 + if (!result)
2086 + result = ce_write(c, fd, ce->name, len);
2087 + if (!result)
2088 + result = ce_write(c, fd, padding, align_padding_size(size, len));
2089 } else {
2090 int common, to_remove, prefix_size;
2091 unsigned char to_remove_vi[16];
@@ -2094,16 +2098,12 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
2098 to_remove = previous_name->len - common;
2099 prefix_size = encode_varint(to_remove, to_remove_vi);
2100
2097 - if (ce->ce_flags & CE_EXTENDED)
2098 - size = offsetof(struct ondisk_cache_entry_extended, name);
2099 - else
2100 - size = offsetof(struct ondisk_cache_entry, name);
2101 - size += prefix_size + (ce_namelen(ce) - common + 1);
2102 -
2103 - ondisk = xcalloc(1, size);
2104 - name = copy_cache_entry_to_ondisk(ondisk, ce);
2105 - memcpy(name, to_remove_vi, prefix_size);
2106 - memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
2101 + copy_cache_entry_to_ondisk(ondisk, ce);
2102 + result = ce_write(c, fd, ondisk, size);
2103 + if (!result)
2104 + result = ce_write(c, fd, to_remove_vi, prefix_size);
2105 + if (!result)
2106 + result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common + 1);
2107
2108 strbuf_splice(previous_name, common, to_remove,
2109 ce->name + common, ce_namelen(ce) - common);
@@ -2113,8 +2113,6 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
2113 ce->ce_flags &= ~CE_STRIP_NAME;
2114 }
2115
2116 - result = ce_write(c, fd, ondisk, size);
2117 - free(ondisk);
2116 return result;
2117 }
2118
@@ -2196,6 +2194,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2194 struct cache_entry **cache = istate->cache;
2195 int entries = istate->cache_nr;
2196 struct stat st;
2197 + struct ondisk_cache_entry_extended ondisk;
2198 struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
2199 int drop_cache_tree = 0;
2200
@@ -2232,6 +2231,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2231 return -1;
2232
2233 previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
2234 +
2235 for (i = 0; i < entries; i++) {
2236 struct cache_entry *ce = cache[i];
2237 if (ce->ce_flags & CE_REMOVE)
@@ -2251,7 +2251,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2251
2252 drop_cache_tree = 1;
2253 }
2254 - if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
2254 + if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
2255 err = -1;
2256
2257 if (err)