ieot: add Index Entry Offset Table (IEOT) extension

This patch enables addressing the CPU cost of loading the index by adding additional data to the index that will allow us to efficiently multi- thread the loading and conversion of cache entries. It accomplishes this by adding an (optional) index extension that is a table of offsets to blocks of cache entries in the index file. To make this work for V4 indexes, when writing the cache entries, it periodically "resets" the prefix-compression by encoding the current entry as if the path name for the previous entry is completely different and saves the offset of that entry in the IEOT. Basically, with V4 indexes, it generates offsets into blocks of prefix-compressed entries. Signed-off-by: Ben Peart <benpeart@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ben Peart committed Oct 10, 2018 at 11:59 UTC 3255089ada6fc8f18d41dfc37cf66d7af994603d
2 files changed +211 -3
Documentation/technical/index-format.txt
+18
@@ -337,3 +337,21 @@ The remaining data of each directory block is grouped by type:
337
338 SHA-1("TREE" + <binary representation of N> +
339 "REUC" + <binary representation of M>)
340 +
341 +== Index Entry Offset Table
342 +
343 + The Index Entry Offset Table (IEOT) is used to help address the CPU
344 + cost of loading the index by enabling multi-threading the process of
345 + converting cache entries from the on-disk format to the in-memory format.
346 + The signature for this extension is { 'I', 'E', 'O', 'T' }.
347 +
348 + The extension consists of:
349 +
350 + - 32-bit version (currently 1)
351 +
352 + - A number of index offset entries each consisting of:
353 +
354 + - 32-bit offset from the begining of the file to the first cache entry
355 + in this block of entries.
356 +
357 + - 32-bit count of cache entries in this block
read-cache.c
+193 -3
@@ -45,6 +45,7 @@
45 #define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
46 #define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
47 #define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
48 +#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
49
50 /* changes that can be kept in $GIT_DIR/index (basically all extensions) */
51 #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
@@ -1696,6 +1697,7 @@ static int read_index_extension(struct index_state *istate,
1697 read_fsmonitor_extension(istate, data, sz);
1698 break;
1699 case CACHE_EXT_ENDOFINDEXENTRIES:
1700 + case CACHE_EXT_INDEXENTRYOFFSETTABLE:
1701 /* already handled in do_read_index() */
1702 break;
1703 default:
@@ -1888,6 +1890,23 @@ static size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
1890 return ondisk_size + entries * per_entry;
1891 }
1892
1893 +struct index_entry_offset
1894 +{
1895 + /* starting byte offset into index file, count of index entries in this block */
1896 + int offset, nr;
1897 +};
1898 +
1899 +struct index_entry_offset_table
1900 +{
1901 + int nr;
1902 + struct index_entry_offset entries[FLEX_ARRAY];
1903 +};
1904 +
1905 +#ifndef NO_PTHREADS
1906 +static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset);
1907 +static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot);
1908 +#endif
1909 +
1910 static size_t read_eoie_extension(const char *mmap, size_t mmap_size);
1911 static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset);
1912
@@ -1929,6 +1948,15 @@ static void *load_index_extensions(void *_data)
1948 return NULL;
1949 }
1950
1951 +/*
1952 + * Mostly randomly chosen maximum thread counts: we
1953 + * cap the parallelism to online_cpus() threads, and we want
1954 + * to have at least 10000 cache entries per thread for it to
1955 + * be worth starting a thread.
1956 + */
1957 +
1958 +#define THREAD_COST (10000)
1959 +
1960 /* remember to discard_cache() before reading a different cache! */
1961 int do_read_index(struct index_state *istate, const char *path, int must_exist)
1962 {
@@ -2521,6 +2549,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2549 struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
2550 int drop_cache_tree = istate->drop_cache_tree;
2551 off_t offset;
2552 + int ieot_blocks = 1;
2553 + struct index_entry_offset_table *ieot = NULL;
2554 + int nr, nr_threads;
2555
2556 for (i = removed = extended = 0; i < entries; i++) {
2557 if (cache[i]->ce_flags & CE_REMOVE)
@@ -2554,10 +2585,44 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2585 if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
2586 return -1;
2587
2588 +#ifndef NO_PTHREADS
2589 + nr_threads = git_config_get_index_threads();
2590 + if (nr_threads != 1) {
2591 + int ieot_blocks, cpus;
2592 +
2593 + /*
2594 + * ensure default number of ieot blocks maps evenly to the
2595 + * default number of threads that will process them leaving
2596 + * room for the thread to load the index extensions.
2597 + */
2598 + if (!nr_threads) {
2599 + ieot_blocks = istate->cache_nr / THREAD_COST;
2600 + cpus = online_cpus();
2601 + if (ieot_blocks > cpus - 1)
2602 + ieot_blocks = cpus - 1;
2603 + } else {
2604 + ieot_blocks = nr_threads;
2605 + }
2606 +
2607 + /*
2608 + * no reason to write out the IEOT extension if we don't
2609 + * have enough blocks to utilize multi-threading
2610 + */
2611 + if (ieot_blocks > 1) {
2612 + ieot = xcalloc(1, sizeof(struct index_entry_offset_table)
2613 + + (ieot_blocks * sizeof(struct index_entry_offset)));
2614 + ieot_blocks = DIV_ROUND_UP(entries, ieot_blocks);
2615 + }
2616 + }
2617 +#endif
2618 +
2619 offset = lseek(newfd, 0, SEEK_CUR);
2558 - if (offset < 0)
2620 + if (offset < 0) {
2621 + free(ieot);
2622 return -1;
2623 + }
2624 offset += write_buffer_len;
2625 + nr = 0;
2626 previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
2627
2628 for (i = 0; i < entries; i++) {
@@ -2579,24 +2644,74 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
2644
2645 drop_cache_tree = 1;
2646 }
2647 + if (ieot && i && (i % ieot_blocks == 0)) {
2648 + ieot->entries[ieot->nr].nr = nr;
2649 + ieot->entries[ieot->nr].offset = offset;
2650 + ieot->nr++;
2651 + /*
2652 + * If we have a V4 index, set the first byte to an invalid
2653 + * character to ensure there is nothing common with the previous
2654 + * entry
2655 + */
2656 + if (previous_name)
2657 + previous_name->buf[0] = 0;
2658 + nr = 0;
2659 + offset = lseek(newfd, 0, SEEK_CUR);
2660 + if (offset < 0) {
2661 + free(ieot);
2662 + return -1;
2663 + }
2664 + offset += write_buffer_len;
2665 + }
2666 if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
2667 err = -1;
2668
2669 if (err)
2670 break;
2671 + nr++;
2672 + }
2673 + if (ieot && nr) {
2674 + ieot->entries[ieot->nr].nr = nr;
2675 + ieot->entries[ieot->nr].offset = offset;
2676 + ieot->nr++;
2677 }
2678 strbuf_release(&previous_name_buf);
2679
2590 - if (err)
2680 + if (err) {
2681 + free(ieot);
2682 return err;
2683 + }
2684
2685 /* Write extension data here */
2686 offset = lseek(newfd, 0, SEEK_CUR);
2595 - if (offset < 0)
2687 + if (offset < 0) {
2688 + free(ieot);
2689 return -1;
2690 + }
2691 offset += write_buffer_len;
2692 the_hash_algo->init_fn(&eoie_c);
2693
2694 + /*
2695 + * Lets write out CACHE_EXT_INDEXENTRYOFFSETTABLE first so that we
2696 + * can minimize the number of extensions we have to scan through to
2697 + * find it during load. Write it out regardless of the
2698 + * strip_extensions parameter as we need it when loading the shared
2699 + * index.
2700 + */
2701 +#ifndef NO_PTHREADS
2702 + if (ieot) {
2703 + struct strbuf sb = STRBUF_INIT;
2704 +
2705 + write_ieot_extension(&sb, ieot);
2706 + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0
2707 + || ce_write(&c, newfd, sb.buf, sb.len) < 0;
2708 + strbuf_release(&sb);
2709 + free(ieot);
2710 + if (err)
2711 + return -1;
2712 + }
2713 +#endif
2714 +
2715 if (!strip_extensions && istate->split_index) {
2716 struct strbuf sb = STRBUF_INIT;
2717
@@ -3180,3 +3295,78 @@ static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context,
3295 the_hash_algo->final_fn(hash, eoie_context);
3296 strbuf_add(sb, hash, the_hash_algo->rawsz);
3297 }
3298 +
3299 +#ifndef NO_PTHREADS
3300 +#define IEOT_VERSION (1)
3301 +
3302 +static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset)
3303 +{
3304 + const char *index = NULL;
3305 + uint32_t extsize, ext_version;
3306 + struct index_entry_offset_table *ieot;
3307 + int i, nr;
3308 +
3309 + /* find the IEOT extension */
3310 + if (!offset)
3311 + return NULL;
3312 + while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
3313 + extsize = get_be32(mmap + offset + 4);
3314 + if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) {
3315 + index = mmap + offset + 4 + 4;
3316 + break;
3317 + }
3318 + offset += 8;
3319 + offset += extsize;
3320 + }
3321 + if (!index)
3322 + return NULL;
3323 +
3324 + /* validate the version is IEOT_VERSION */
3325 + ext_version = get_be32(index);
3326 + if (ext_version != IEOT_VERSION) {
3327 + error("invalid IEOT version %d", ext_version);
3328 + return NULL;
3329 + }
3330 + index += sizeof(uint32_t);
3331 +
3332 + /* extension size - version bytes / bytes per entry */
3333 + nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t));
3334 + if (!nr) {
3335 + error("invalid number of IEOT entries %d", nr);
3336 + return NULL;
3337 + }
3338 + ieot = xmalloc(sizeof(struct index_entry_offset_table)
3339 + + (nr * sizeof(struct index_entry_offset)));
3340 + ieot->nr = nr;
3341 + for (i = 0; i < nr; i++) {
3342 + ieot->entries[i].offset = get_be32(index);
3343 + index += sizeof(uint32_t);
3344 + ieot->entries[i].nr = get_be32(index);
3345 + index += sizeof(uint32_t);
3346 + }
3347 +
3348 + return ieot;
3349 +}
3350 +
3351 +static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot)
3352 +{
3353 + uint32_t buffer;
3354 + int i;
3355 +
3356 + /* version */
3357 + put_be32(&buffer, IEOT_VERSION);
3358 + strbuf_add(sb, &buffer, sizeof(uint32_t));
3359 +
3360 + /* ieot */
3361 + for (i = 0; i < ieot->nr; i++) {
3362 +
3363 + /* offset */
3364 + put_be32(&buffer, ieot->entries[i].offset);
3365 + strbuf_add(sb, &buffer, sizeof(uint32_t));
3366 +
3367 + /* count */
3368 + put_be32(&buffer, ieot->entries[i].nr);
3369 + strbuf_add(sb, &buffer, sizeof(uint32_t));
3370 + }
3371 +}
3372 +#endif