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

[PATCH v4 2/4] read-cache: add index.skipHash config option

From
Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Date
Dec 16, 2022, 15:31 UTC
Message-ID
<aae047cbc9f62654c7bd84fbc7ba6c8dfdc3b234.1671204678.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.1439.v4.git.1671204678.gitgitgadget@gmail.com>
From: Derrick Stolee <derrickstolee@github.com>

The previous change allowed skipping the hashing portion of the hashwrite API, using it instead as a buffered write API. Disabling the hashwrite can be particularly helpful when the write operation is in a critical path.

One such critical path is the writing of the index. This operation is so critical that the sparse index was created specifically to reduce the size of the index to make these writes (and reads) faster.

This trade-off between file stability at rest and write-time performance is not easy to balance. The index is an interesting case for a couple reasons:

1. Writes block users. Writing the index takes place in many user-
   blocking foreground operations. The speed improvement directly
   impacts their use. Other file formats are typically written in the
   background (commit-graph, multi-pack-index) or are super-critical to
   correctness (pack-files).
2. Index files are short lived. It is rare that a user leaves an index
   for a long time with many staged changes. Outside of staged changes,
   the index can be completely destroyed and rewritten with minimal
   impact to the user.

Following a similar approach to one used in the microsoft/git fork [1], add a new config option (index.skipHash) that allows disabling this hashing during the index write. The cost is that we can no longer validate the contents for corruption-at-rest using the trailing hash.

[1] https://github.com/microsoft/git/commit/21fed2d91410f45d85279467f21d717a2db45201

We load this config from the repository config given by istate->repo, with a fallback to the_repository if it is not set.

While older Git versions will not recognize the null hash as a special case, the file format itself is still being met in terms of its structure. Using this null hash will still allow Git operations to function across older versions.

The one exception is 'git fsck' which checks the hash of the index file. This used to be a check on every index read, but was split out to just the index in a33fc72fe91 (read-cache: force_verify_index_checksum, 2017-04-14) and released first in Git 2.13.0. Document the versions that relaxed these restrictions, with the optimistic expectation that this change will be included in Git 2.40.0.

Here, we disable this check if the trailing hash is all zeroes. We add a warning to the config option that this may cause undesirable behavior with older Git versions.

As a quick comparison, I tested 'git update-index --force-write' with and without index.skipHash=true on a copy of the Linux kernel repository.

Benchmark 1: with hash
  Time (mean ± σ):      46.3 ms ±  13.8 ms    [User: 34.3 ms, System: 11.9 ms]
  Range (min … max):    34.3 ms …  79.1 ms    82 runs
Benchmark 2: without hash
  Time (mean ± σ):      26.0 ms ±   7.9 ms    [User: 11.8 ms, System: 14.2 ms]
  Range (min … max):    16.3 ms …  42.0 ms    69 runs
Summary
  'without hash' ran
    1.78 ± 0.76 times faster than 'with hash'

These performance benefits are substantial enough to allow users the ability to opt-in to this feature, even with the potential confusion with older 'git fsck' versions.

It is critical that this test is placed before the test_index_version tests, since those tests obliterate the .git/config file and hence lose the setting from GIT_TEST_DEFAULT_HASH, if set.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
---
 Documentation/config/index.txt | 11 +++++++++++
 read-cache.c                   | 13 ++++++++++++-
 t/t1600-index.sh               |  6 ++++++
 3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/Documentation/config/index.txt b/Documentation/config/index.txt
index 75f3a2d1054..23c7985eb40 100644
--- a/Documentation/config/index.txt
+++ b/Documentation/config/index.txt
@@ -30,3 +30,14 @@ index.version::
 	Specify the version with which new index files should be
 	initialized.  This does not affect existing repositories.
 	If `feature.manyFiles` is enabled, then the default is 4.
+
+index.skipHash::
+	When enabled, do not compute the trailing hash for the index file.
+	This accelerates Git commands that manipulate the index, such as
+	`git add`, `git commit`, or `git status`. Instead of storing the
+	checksum, write a trailing set of bytes with value zero, indicating
+	that the computation was skipped.
++
+If you enable `index.skipHash`, then Git clients older than 2.13.0 will
+refuse to parse the index and Git clients older than 2.40.0 will report an
+error during `git fsck`.
diff --git a/read-cache.c b/read-cache.c
index 46f5e497b14..fde0697873c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1817,6 +1817,8 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size)
 	git_hash_ctx c;
 	unsigned char hash[GIT_MAX_RAWSZ];
 	int hdr_version;
+	unsigned char *start, *end;
+	struct object_id oid;
 
 	if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
 		return error(_("bad signature 0x%08x"), hdr->hdr_signature);
@@ -1827,10 +1829,16 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size)
 	if (!verify_index_checksum)
 		return 0;
 
+	end = (unsigned char *)hdr + size;
+	start = end - the_hash_algo->rawsz;
+	oidread(&oid, start);
+	if (oideq(&oid, null_oid()))
+		return 0;
+
 	the_hash_algo->init_fn(&c);
 	the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
 	the_hash_algo->final_fn(hash, &c);
-	if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
+	if (!hasheq(hash, start))
 		return error(_("bad index file sha1 signature"));
 	return 0;
 }
@@ -2915,9 +2923,12 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
 	int ieot_entries = 1;
 	struct index_entry_offset_table *ieot = NULL;
 	int nr, nr_threads;
+	struct repository *r = istate->repo ? istate->repo : the_repository;
 
 	f = hashfd(tempfile->fd, tempfile->filename.buf);
 
+	repo_config_get_maybe_bool(r, "index.skiphash", &f->skip_hash);
+
 	for (i = removed = extended = 0; i < entries; i++) {
 		if (cache[i]->ce_flags & CE_REMOVE)
 			removed++;
diff --git a/t/t1600-index.sh b/t/t1600-index.sh
index 010989f90e6..45feb0fc5d8 100755
--- a/t/t1600-index.sh
+++ b/t/t1600-index.sh
@@ -65,6 +65,12 @@ test_expect_success 'out of bounds index.version issues warning' '
 	)
 '
 
+test_expect_success 'index.skipHash config option' '
+	rm -f .git/index &&
+	git -c index.skipHash=true add a &&
+	git fsck
+'
+
 test_index_version () {
 	INDEX_VERSION_CONFIG=$1 &&
 	FEATURE_MANY_FILES=$2 &&
-- 
gitgitgadget
Previous: Derrick Stolee via GitGitGadgetNext: Derrick Stolee via GitGitGadget
Message 43 of 59 in “Optionally skip hashing index on write”
  1. 0/4 Optionally skip hashing index on writeDerrick Stolee via GitGitGadget, Dec 7, 2022
  2. 2/4 read-cache: add index.skipHash config optionDerrick Stolee via GitGitGadget, Dec 7, 2022
  3. Eric SunshineDec 7, 2022
  4. Derrick StoleeDec 12, 2022
  5. Eric SunshineDec 12, 2022
  6. Ævar Arnfjörð BjarmasonDec 7, 2022
  7. Ævar Arnfjörð BjarmasonDec 7, 2022
  8. Junio C HamanoDec 8, 2022
  9. Derrick StoleeDec 12, 2022
  10. Ævar Arnfjörð BjarmasonDec 12, 2022
  11. 1/4 hashfile: allow skipping the hash functionDerrick Stolee via GitGitGadget, Dec 7, 2022
  12. Ævar Arnfjörð BjarmasonDec 7, 2022
  13. Jeff KingDec 8, 2022
  14. 3/4 test-lib-functions: add helper for trailing hashDerrick Stolee via GitGitGadget, Dec 7, 2022
  15. Ævar Arnfjörð BjarmasonDec 7, 2022
  16. Derrick StoleeDec 12, 2022
  17. 4/4 features: feature.manyFiles implies fast index writesDerrick Stolee via GitGitGadget, Dec 7, 2022
  18. Ævar Arnfjörð BjarmasonDec 7, 2022
  19. Derrick StoleeDec 12, 2022
  20. Ævar Arnfjörð BjarmasonDec 12, 2022
  21. Junio C HamanoDec 7, 2022
  22. Ævar Arnfjörð BjarmasonDec 7, 2022
  23. Derrick StoleeDec 8, 2022
  24. Jacob KellerDec 12, 2022
  25. 0/4 Optionally skip hashing index on writeDerrick Stolee via GitGitGadget, Dec 12, 2022
  26. 1/4 hashfile: allow skipping the hash functionDerrick Stolee via GitGitGadget, Dec 12, 2022
  27. 4/4 features: feature.manyFiles implies fast index writesDerrick Stolee via GitGitGadget, Dec 12, 2022
  28. 2/4 read-cache: add index.skipHash config optionDerrick Stolee via GitGitGadget, Dec 12, 2022
  29. 3/4 test-lib-functions: add helper for trailing hashDerrick Stolee via GitGitGadget, Dec 12, 2022
  30. SZEDER GáborDec 12, 2022
  31. Junio C HamanoDec 13, 2022
  32. SZEDER GáborDec 17, 2022
  33. 0/4 Optionally skip hashing index on writeDerrick Stolee via GitGitGadget, Dec 15, 2022
  34. 1/4 hashfile: allow skipping the hash functionDerrick Stolee via GitGitGadget, Dec 15, 2022
  35. 2/4 read-cache: add index.skipHash config optionDerrick Stolee via GitGitGadget, Dec 15, 2022
  36. Ævar Arnfjörð BjarmasonDec 15, 2022
  37. 3/4 test-lib-functions: add helper for trailing hashDerrick Stolee via GitGitGadget, Dec 15, 2022
  38. 4/4 features: feature.manyFiles implies fast index writesDerrick Stolee via GitGitGadget, Dec 15, 2022
  39. Ævar Arnfjörð BjarmasonDec 15, 2022
  40. Derrick StoleeDec 16, 2022
  41. 0/4 Optionally skip hashing index on writeDerrick Stolee via GitGitGadget, Dec 16, 2022
  42. 1/4 hashfile: allow skipping the hash functionDerrick Stolee via GitGitGadget, Dec 16, 2022
  43. 2/4 read-cache: add index.skipHash config optionDerrick Stolee via GitGitGadget, Dec 16, 2022
  44. 3/4 test-lib-functions: add helper for trailing hashDerrick Stolee via GitGitGadget, Dec 16, 2022
  45. 4/4 features: feature.manyFiles implies fast index writesDerrick Stolee via GitGitGadget, Dec 16, 2022
  46. Ævar Arnfjörð BjarmasonDec 16, 2022
  47. Derrick StoleeJan 6, 2023
  48. Junio C HamanoJan 6, 2023
  49. Derrick StoleeJan 6, 2023
  50. Ævar Arnfjörð BjarmasonJan 9, 2023
  51. Derrick StoleeJan 9, 2023
  52. Ævar Arnfjörð BjarmasonJan 9, 2023
  53. 0/4 Optionally skip hashing index on writeDerrick Stolee via GitGitGadget, Jan 6, 2023
  54. 1/4 hashfile: allow skipping the hash functionDerrick Stolee via GitGitGadget, Jan 6, 2023
  55. 2/4 read-cache: add index.skipHash config optionDerrick Stolee via GitGitGadget, Jan 6, 2023
  56. 3/4 test-lib-functions: add helper for trailing hashDerrick Stolee via GitGitGadget, Jan 6, 2023
  57. 4/4 features: feature.manyFiles implies fast index writesDerrick Stolee via GitGitGadget, Jan 6, 2023
  58. Junio C HamanoJan 15, 2023
  59. Derrick StoleeJan 17, 2023

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.