]> git.ipfire.org Git - thirdparty/git.git/commitdiff
test-tool: add helper for name-hash values
authorDerrick Stolee <stolee@gmail.com>
Mon, 27 Jan 2025 19:02:33 +0000 (19:02 +0000)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Jan 2025 21:21:43 +0000 (13:21 -0800)
Add a new test-tool helper, name-hash, to output the value of the
name-hash algorithms for the input list of strings, one per line.

Since the name-hash values can be stored in the .bitmap files, it is
important that these hash functions do not change across Git versions.
Add a simple test to t5310-pack-bitmaps.sh to provide some testing of
the current values. Due to how these functions are implemented, it would
be difficult to change them without disturbing these values. The paths
used for this test are carefully selected to demonstrate some of the
behavior differences of the two current name hash versions, including
which conditions will cause them to collide.

Create a performance test that uses test_size to demonstrate how
collisions occur for these hash algorithms. This test helps inform
someone as to the behavior of the name-hash algorithms for their repo
based on the paths at HEAD.

My copy of the Git repository shows modest statistics around the
collisions of the default name-hash algorithm:

Test                               this tree
--------------------------------------------------
5314.1: paths at head                         4.5K
5314.2: distinct hash value: v1               4.1K
5314.3: maximum multiplicity: v1                13
5314.4: distinct hash value: v2               4.2K
5314.5: maximum multiplicity: v2                 9

Here, the maximum collision multiplicity is 13, but around 10% of paths
have a collision with another path.

In a more interesting example, the microsoft/fluentui [1] repo had these
statistics at time of committing:

Test                               this tree
--------------------------------------------------
5314.1: paths at head                        19.5K
5314.2: distinct hash value: v1               8.2K
5314.3: maximum multiplicity: v1               279
5314.4: distinct hash value: v2              17.8K
5314.5: maximum multiplicity: v2                44

[1] https://github.com/microsoft/fluentui

That demonstrates that of the nearly twenty thousand path names, they
are assigned around eight thousand distinct values. 279 paths are
assigned to a single value, leading the packing algorithm to sort
objects from those paths together, by size.

With the v2 name hash function, the maximum multiplicity lowers to 44,
leaving some room for further improvement.

In a more extreme example, an internal monorepo had a much worse
collision rate:

Test                               this tree
--------------------------------------------------
5314.1: paths at head                       227.3K
5314.2: distinct hash value: v1              72.3K
5314.3: maximum multiplicity: v1             14.4K
5314.4: distinct hash value: v2             166.5K
5314.5: maximum multiplicity: v2               138

Here, we can see that the v2 name hash function provides somem
improvements, but there are still a number of collisions that could lead
to repacking problems at this scale.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Makefile
t/helper/test-name-hash.c [new file with mode: 0644]
t/helper/test-tool.c
t/helper/test-tool.h
t/perf/p5314-name-hash.sh [new file with mode: 0755]
t/t5310-pack-bitmaps.sh

index 6f5986b66eae6ae3589c2a374a1eaaf41f90796f..65403f6dd09c4f95bcdc18ae7a7fcbf9fc509f81 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -816,6 +816,7 @@ TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
 TEST_BUILTINS_OBJS += test-match-trees.o
 TEST_BUILTINS_OBJS += test-mergesort.o
 TEST_BUILTINS_OBJS += test-mktemp.o
+TEST_BUILTINS_OBJS += test-name-hash.o
 TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-pack-mtimes.o
 TEST_BUILTINS_OBJS += test-parse-options.o
diff --git a/t/helper/test-name-hash.c b/t/helper/test-name-hash.c
new file mode 100644 (file)
index 0000000..af1d52d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * test-name-hash.c: Read a list of paths over stdin and report on their
+ * name-hash and full name-hash.
+ */
+
+#include "test-tool.h"
+#include "git-compat-util.h"
+#include "pack-objects.h"
+#include "strbuf.h"
+
+int cmd__name_hash(int argc UNUSED, const char **argv UNUSED)
+{
+       struct strbuf line = STRBUF_INIT;
+
+       while (!strbuf_getline(&line, stdin)) {
+               printf("%10u ", pack_name_hash(line.buf));
+               printf("%10u ", pack_name_hash_v2((unsigned const char *)line.buf));
+               printf("%s\n", line.buf);
+       }
+
+       strbuf_release(&line);
+       return 0;
+}
index 1ebb69a5dc4c1716fe21e1640eecf5f5f05c130d..e794058ab6dcee9fb12468f5fa3d51dc7beb844f 100644 (file)
@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {
        { "match-trees", cmd__match_trees },
        { "mergesort", cmd__mergesort },
        { "mktemp", cmd__mktemp },
+       { "name-hash", cmd__name_hash },
        { "online-cpus", cmd__online_cpus },
        { "pack-mtimes", cmd__pack_mtimes },
        { "parse-options", cmd__parse_options },
index 21802ac27da37f8730c2f68cdd8d82932e2dcc92..26ff30a5a9abfe0747c71e2ba8f9d54719a5824e 100644 (file)
@@ -37,6 +37,7 @@ int cmd__lazy_init_name_hash(int argc, const char **argv);
 int cmd__match_trees(int argc, const char **argv);
 int cmd__mergesort(int argc, const char **argv);
 int cmd__mktemp(int argc, const char **argv);
+int cmd__name_hash(int argc, const char **argv);
 int cmd__online_cpus(int argc, const char **argv);
 int cmd__pack_mtimes(int argc, const char **argv);
 int cmd__parse_options(int argc, const char **argv);
diff --git a/t/perf/p5314-name-hash.sh b/t/perf/p5314-name-hash.sh
new file mode 100755 (executable)
index 0000000..4ef0ba7
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='Tests pack performance using bitmaps'
+. ./perf-lib.sh
+
+GIT_TEST_PASSING_SANITIZE_LEAK=0
+export GIT_TEST_PASSING_SANITIZE_LEAK
+
+test_perf_large_repo
+
+test_size 'paths at head' '
+       git ls-tree -r --name-only HEAD >path-list &&
+       wc -l <path-list &&
+       test-tool name-hash <path-list >name-hashes
+'
+
+for version in 1 2
+do
+       test_size "distinct hash value: v$version" '
+               awk "{ print \$$version; }" <name-hashes | sort | \
+                       uniq -c >name-hash-count &&
+               wc -l <name-hash-count
+       '
+
+       test_size "maximum multiplicity: v$version" '
+               sort -nr <name-hash-count | head -n 1 | \
+                       awk "{ print \$1; }"
+       '
+done
+
+test_done
index c30522b57fd74da561c6e1dfa24053bd7c08fbaa..871ce01401a11e7cbe70e0c5e8c47c540ea17d1c 100755 (executable)
@@ -27,6 +27,36 @@ has_any () {
        grep -Ff "$1" "$2"
 }
 
+# Since name-hash values are stored in the .bitmap files, add a test
+# that checks that the name-hash calculations are stable across versions.
+# Not exhaustive, but these hashing algorithms would be hard to change
+# without causing deviations here.
+test_expect_success 'name-hash value stability' '
+       cat >names <<-\EOF &&
+       first
+       second
+       third
+       a/one-long-enough-for-collisions
+       b/two-long-enough-for-collisions
+       many/parts/to/this/path/enough/to/collide/in/v2
+       enough/parts/to/this/path/enough/to/collide/in/v2
+       EOF
+
+       test-tool name-hash <names >out &&
+
+       cat >expect <<-\EOF &&
+       2582249472 1763573760 first
+       2289942528 1188134912 second
+       2300837888 1130758144 third
+       2544516325 3963087891 a/one-long-enough-for-collisions
+       2544516325 4013419539 b/two-long-enough-for-collisions
+       1420111091 1709547268 many/parts/to/this/path/enough/to/collide/in/v2
+       1420111091 1709547268 enough/parts/to/this/path/enough/to/collide/in/v2
+       EOF
+
+       test_cmp expect out
+'
+
 test_bitmap_cases () {
        writeLookupTable=false
        for i in "$@"