Skip to content

Commit 3255089

Browse files
benpeartgitster
authored andcommitted
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>
1 parent abb4bb8 commit 3255089

File tree

2 files changed

+211
-3
lines changed

2 files changed

+211
-3
lines changed

Documentation/technical/index-format.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,21 @@ The remaining data of each directory block is grouped by type:
337337

338338
SHA-1("TREE" + <binary representation of N> +
339339
"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

Lines changed: 193 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
4646
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
4747
#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */
48+
#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */
4849

4950
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
5051
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
@@ -1696,6 +1697,7 @@ static int read_index_extension(struct index_state *istate,
16961697
read_fsmonitor_extension(istate, data, sz);
16971698
break;
16981699
case CACHE_EXT_ENDOFINDEXENTRIES:
1700+
case CACHE_EXT_INDEXENTRYOFFSETTABLE:
16991701
/* already handled in do_read_index() */
17001702
break;
17011703
default:
@@ -1888,6 +1890,23 @@ static size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
18881890
return ondisk_size + entries * per_entry;
18891891
}
18901892

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+
18911910
static size_t read_eoie_extension(const char *mmap, size_t mmap_size);
18921911
static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset);
18931912

@@ -1929,6 +1948,15 @@ static void *load_index_extensions(void *_data)
19291948
return NULL;
19301949
}
19311950

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+
19321960
/* remember to discard_cache() before reading a different cache! */
19331961
int do_read_index(struct index_state *istate, const char *path, int must_exist)
19341962
{
@@ -2521,6 +2549,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
25212549
struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
25222550
int drop_cache_tree = istate->drop_cache_tree;
25232551
off_t offset;
2552+
int ieot_blocks = 1;
2553+
struct index_entry_offset_table *ieot = NULL;
2554+
int nr, nr_threads;
25242555

25252556
for (i = removed = extended = 0; i < entries; i++) {
25262557
if (cache[i]->ce_flags & CE_REMOVE)
@@ -2554,10 +2585,44 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
25542585
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
25552586
return -1;
25562587

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+
25572619
offset = lseek(newfd, 0, SEEK_CUR);
2558-
if (offset < 0)
2620+
if (offset < 0) {
2621+
free(ieot);
25592622
return -1;
2623+
}
25602624
offset += write_buffer_len;
2625+
nr = 0;
25612626
previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
25622627

25632628
for (i = 0; i < entries; i++) {
@@ -2579,24 +2644,74 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
25792644

25802645
drop_cache_tree = 1;
25812646
}
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+
}
25822666
if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
25832667
err = -1;
25842668

25852669
if (err)
25862670
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++;
25872677
}
25882678
strbuf_release(&previous_name_buf);
25892679

2590-
if (err)
2680+
if (err) {
2681+
free(ieot);
25912682
return err;
2683+
}
25922684

25932685
/* Write extension data here */
25942686
offset = lseek(newfd, 0, SEEK_CUR);
2595-
if (offset < 0)
2687+
if (offset < 0) {
2688+
free(ieot);
25962689
return -1;
2690+
}
25972691
offset += write_buffer_len;
25982692
the_hash_algo->init_fn(&eoie_c);
25992693

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+
26002715
if (!strip_extensions && istate->split_index) {
26012716
struct strbuf sb = STRBUF_INIT;
26022717

@@ -3180,3 +3295,78 @@ static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context,
31803295
the_hash_algo->final_fn(hash, eoie_context);
31813296
strbuf_add(sb, hash, the_hash_algo->rawsz);
31823297
}
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

0 commit comments

Comments
 (0)