Skip to content

libgitcore: add sha1dc as an optional feature - #6438

Open
dscho wants to merge 4 commits into
git-for-windows:mainfrom
dscho:optionally-use-sha1dc-rs
Open

dscho wants to merge 4 commits into
git-for-windows:mainfrom
dscho:optionally-use-sha1dc-rs

Conversation

@dscho

@dscho dscho commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

The new Rust crate sha1dc promises not only type safety but also much better performance compared to the collision-detecting SHA-1 library Git uses at the moment. This performance comes mostly from a SIMD-centric design that relies on features provided by many x86_64 and aarch64 CPUs.

Let's optionally use this crate, toggled by the build option DC_SHA1_RS.

In a test on a Ryzen 7 machine with a ~140MB pack, Rust-backed git-index-pack.exe was 2.80x faster by median wall time. Five C/Rust pairs ran in a randomized, balanced order with --verify --no-rev-index --threads=1 --object-format=sha1:

SHA1DC backend Median Best of five
C (default) 24.1s 23.4s
Rust (-rs) 8.6s 8.5s

Note that the sha1dc crate requires a significantly newer Rust version than what Git's existing Rust support requires: 1.87 instead of 1.63.

@dscho dscho self-assigned this Sep 24, 2026
@dscho
dscho force-pushed the optionally-use-sha1dc-rs branch from b5902e6 to d7e5ea8 Compare September 25, 2026 06:43
@srijs

srijs commented Sep 25, 2026

Copy link
Copy Markdown

@dscho fyi, just published 0.1.3 which is another 9–11% faster than 0.1.2 on large inputs, and 32–39%
faster on messages of up to 64 bytes. In case you want to pull this instead :)

@dscho

dscho commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

@srijs very nice!

@dscho

dscho commented Sep 25, 2026

Copy link
Copy Markdown
Member Author

@srijs hmm. I cannot see any change in speed... On my Ryzen 7 (under higher ambient load than I performed the earlier benchmark):

SHA1DC backend Median Best of five
C (default) 32.9s 32.1s
Rust 0.1.2 10.9s 10.4s
Rust 0.1.3 10.7s 10.5s

By the way, my colleague ran this on a Mac and got this:

SHA1DC backend Median Best of five
C (default) 8.035s 8.009s
Rust 0.1.2 4.359s 4.338s
Rust 0.1.3 4.361s 4.268s

Naturally, M4s are much faster than Ryzens, even if the difference between C and Rust is not quite as pronounced on the former ;-)

But as you see, the difference in performance between v0.1.2 and v0.1.3 is basically lost in the noise in our experiments. Maybe your speed improvements target different CPUs?

@srijs

srijs commented Sep 25, 2026 •

Copy link
Copy Markdown

Huh, that's interesting... this is the same git-index-pack benchmark you used before? Do you have a sense on how much % of time is now left being spent on SHA-1 hashing vs. the compression and the rest of the workload, i.e. how we should expect a ~10% hashing improvement to translate to index pack performance proportionally?

I rechecked again multiple times, and I still see the ~10% improvement very clearly. FWIW, my testing set up is the criterion harness that's checked into the sha1dc repo, ran on an Apple M4 laptop as well as Intel Xeon (EC2 c7i.metal-24xl) and AWS Graviton 4 (EC2 c8g.metal-24xl).

Some thoughts:

  • Given I am testing the SHA-1 hashing in isolation and over many iterations, not sure how much of that effect could be warm instruction cache, branch predictor, etc vs mixed workload?
  • I wonder how differences between AMD and Intel processors could play into it here? I fine-tuned the AVX2 search parameters on Intel only so far, so any differences in SIMD execution, branch prediction, etc may have thrown that off?
  • My tests are mostly on random data, whereas presumably your git repo is not. Not sure if that could be an influence too?

In any case, I guess at least it's not gotten worse 🤣

That's a good reality check though; I'll see if I need to improve my benchmarking set up for future experiments.

PS: If you have the time and are inclined, I wonder if you'd get different results if you ran the benchmarks in the sha1dc repo. You can use the --save-baseline flag on Criterion to save results for 0.1.2 and then it'll automatically compare on the next run.

PS2: Oh also, I don't know how much it makes a difference, but I noticed you heap allocate the hasher, vs. both my own benchmark as well as gitoxide stack allocate it. Part of my improvements esp. for smaller inputs was a smaller hasher struct 🤷

@dscho

dscho commented Sep 26, 2026

Copy link
Copy Markdown
Member Author

I have now performed the same benchmark in a Windows Subsystem for Linux instance on the same Windows machine:

SHA1DC backend Median Best of five
C (default) 23.971s 23.435s
Rust 0.1.2 9.123s 8.492s
Rust 0.1.3 9.020s 8.491s

You can clearly see that filesystem performance seems to contribute, as I copied the packfile in question to the ext4 filesystem. But again, the difference between 0.1.2 and 0.1.3 is lost in the noise.

Of course, it's totally possible that I am holding this thing wrong and that simply editing the version number in Cargo.toml followed by touch src/sha1dc_rs.rs && make -j15 DC_SHA1_RS=1 would not pick up the new-and-improved crate...

this is the same git-index-pack benchmark you used before?

@srijs Yes: time ./git index-pack --verify --no-rev-index --threads=1 --object-format=sha1 pack-9bd13bbf01e82d6d29839523b537eba949835fd7.pack. It's a ~140MB packfile that is taken directly from my git-for-windows/git checkout, it was produced some 2 weeks ago (most likely during scheduled maintenance).

Do you have a sense on how much % of time is now left being spent on SHA-1 hashing vs. the compression and the rest of the workload, i.e. how we should expect a ~10% hashing improvement to translate to index pack performance proportionally?

Unfortunately I have no intuition about this. So I started a Linux perf session.

With the default, C version of SHA1DC, 73% of the time is spent in the git_hash_update() call tree, i.e. basically in SHA1DC's sha1_process(). Interesting: a total of 43.66% of that index-pack process are spent in sha1_compression_states(), and 25.88% in ubc_check(). These numbers are from a single run, so take all of that with a grain of salt.

With the Rust sha1dc 0.1.2, it is a totally different picture: resolve_deltas() is the limiting factor, taking 67.63% of the time, and git_hash_update() only takes up 29.58% of the total time (13.42% of the time is spent running sha1dc::ubc_check::avx2::check()).

With 0.1.3, it is a similar picture: 29.30% are spent in the git_hash_update() call tree, i.e. pretty much the same as 0.1.2, maybe a tad less. Again, this is from a single run.

Now, This is a laptop on which I'm running this, and I do like to drive it hard. So the numbers will fluctuate quite a bit, which is why I tried to measure the calls in 5 rounds of randomized order.

I wonder if you'd get different results if you ran the benchmarks in the sha1dc repo. You can use the --save-baseline flag on Criterion to save results for 0.1.2 and then it'll automatically compare on the next run.

That's a good idea! Unfortunately, I am a bit busy with preparations for Git for Windows v2.56 (https://github.com/git-for-windows/git-sdk-64/actions tells me that we're not ready to release), but I will try to remember when things are less hectic.

I don't know how much it makes a difference, but I noticed you heap allocate the hasher, vs. both my own benchmark as well as gitoxide stack allocate it. Part of my improvements esp. for smaller inputs was a smaller hasher struct 🤷

Unfortunately, I cannot use stack allocation here because Git often needs to call git_hash_update() many times for the same stream, and therefore it need the hasher in a form that persists after git_hash_init() returns.

Having said all that, those performance improvements are really exciting to me, both with 0.1.2 and 0.1.3. Thank you so, so much!

@srijs

srijs commented Sep 26, 2026 •

Copy link
Copy Markdown

With the Rust sha1dc 0.1.2, it is a totally different picture: resolve_deltas() is the limiting factor, taking 67.63% of the time, and git_hash_update() only takes up 29.58% of the total time (13.42% of the time is spent running sha1dc::ubc_check::avx2::check()).

Makes sense, I guess at the point where it's <30%, plus all the mixed workloads plus I/O, you might not expect to see the same improvement in the real world. FWIW, in gitoxide these were the gains we saw on M4:

operation sha1dc 0.1.2 sha1dc 0.1.3
gix free pack verify (1 thread) 5.61s 5.37s 1.04x
gix free pack index create (1 thread) 7.29s 7.06s 1.03x
gix free pack verify (all cores) 0.94s 0.90s 1.05x
gix free pack index create (all cores) 2.65s 2.61s 1.02x

That's a good idea! Unfortunately, I am a bit busy with preparations for Git for Windows v2.56 (https://github.com/git-for-windows/git-sdk-64/actions tells me that we're not ready to release), but I will try to remember when things are less hectic.

No worries, I'll work on improving my benchmarking in the meantime. I've already added Zen 3 and 4 to my benchmarking roster to make sure I can cover AMD as well.

Having said all that, those performance improvements are really exciting to me, both with 0.1.2 and 0.1.3. Thank you so, so much!

Appreciate the warm words, and looking forward to seeing this land! LMK if I can help with anything.

The new Rust crate `sha1dc` (https://crates.io/crates/sha1dc) promises
not only type safety but also much better performance compared to the
collision-detecting SHA-1 library Git uses at the moment. This
performance comes mostly from a SIMD-centric design that relies on
features provided by many x86_64 and aarch64 CPUs.

Let's optionally use this crate, toggled by the build option
`DC_SHA1_RS`.

In a pretty unscientific test on a Windows Ryzen 7 machine (UCRT64 GCC
16.2) that was running multiple things in parallel, Rust-backed
`git-index-pack.exe` using `sha1dc` 0.1.3 was 3.08x faster than the C
backend by median wall time.  Five triplets (C, Rust 0.1.2, Rust 0.1.3)
used the same 146,369,017-byte SHA-1 pack in randomized, balanced order
with `--verify --no-rev-index --threads=1 --object-format=sha1`:

SHA1DC backend	Median	Best of five
--------------	------	------------
C (default)	32.9s	32.1s
Rust 0.1.2	10.9s	10.4s
Rust 0.1.3	10.7s	10.5s

Version 0.1.3 finished first in four of five triplets, but its median
advantage over 0.1.2 was only about 2.4%, and the best 0.1.2 run was
faster.

On the same machine, using WSL ("Windows Subsystem for Linux") with the
same packfile copied to Linux' ext4 filesystem:

SHA1DC backend	Median	Best of five
--------------	------	------------
C (default)	23.971s	23.435s
Rust 0.1.2	9.123s	8.492s
Rust 0.1.3	9.020s	8.491s

This is overall faster because of the ext4 vs NTFS performance
characteristics, but the same finding holds true: the Rust version of
`sha1dc` is dramatically faster.

A comparable test on an M4 Mac yields these results:

SHA1DC backend 	Median 	Best of five
-------------- 	------ 	------------
C (default) 	8.035s 	8.009s
Rust 0.1.2 	4.359s 	4.338s
Rust 0.1.3 	4.361s 	4.268s

Note that the `sha1dc` crate still requires a significantly newer Rust
version than Git's existing Rust support requires: 1.87 instead of
1.63 (https://crates.io/api/v1/crates/sha1dc/0.1.3).

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho
dscho force-pushed the optionally-use-sha1dc-rs branch from d7e5ea8 to 9b1c48d Compare September 26, 2026 20:48
The Rust `sha1dc` create is really new. While it produced only correct
hashes in my hands, before unleashing this to the masses, we need to
provide an "escape hatch" in case it doesn't do the right thing.

Therefore, when building with `DC_SHA1_RS`, use the Rust `sha1dc` by
default, yet also offer to use the C version of `sha1dc` via
`core.sha1dcBackend=c` (and `core.sha1dcBackend=rust` to select Rust
explicitly).

This is made possible by a set of function pointers that are initialized
upon the first call to the `git_hash_init()` function.

Note that the order in which `hex.h` and `sha1dc_git.h` are included in
`sha1dc_git.c` now have to be turned the other way round, to avoid
redefining the `platform_SHA*` constants.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
I am about to introduce logic that needs to perform some initialization
once, and once only, even if called concurrently.

This is a perfect job for `pthread_once()`, but Git's source code
currently lacks a Win32 shim. So let's add one!

Also provide a trivial shim for `NO_PTHREAD` builds.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `sha1dc_init` function pointer points to a function initially that
determines which sha1dc backend to use. Naturally, this initialization
should only run once.

To allow for that function to be called concurrently, we need to use a
pthread primitive to ensure that the `sha1dc_*()` function pointers are
initialized exactly once.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho
dscho force-pushed the optionally-use-sha1dc-rs branch from 9b1c48d to 52c8e4e Compare September 26, 2026 21:07
dscho added a commit to dscho/git-sdk-64 that referenced this pull request Sep 26, 2026
The purpose of the `build-installers` SDK artifact is that it offers all
the bits and pieces that are needed to build a full Git for Windows
installer, including everything to build a new `mingw-w64-git` package.

We are about to require Rust to do so, as the Rust parts of Git's source
code will become mandatory with Git v3.0, and Git for Windows will
already enable them after v2.56.

To do so, let's add a workflow_dispatch trigger to the `ci-artifacts`
workflow (which _already_ has everything needed to build the
`build-installers` SDK artifact), accept a git-for-windows/git PR URL to
build from, and then go and build a full installer.

In preparation for git-for-windows/git#6438,
which integrates an incredibly fast, yet still collision-detecting SHA-1
Rust crate, enable DC_SHA1_RS and leave NO_RUST unset when Rust is
installed; otherwise build without Rust. Upload the installer as an
Actions artifact for inspection.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Cherry-picked-from: e604906 (ci-artifacts: offer to exercise the `build-installers` artifact, 2026-09-25)
dscho added a commit to dscho/git-sdk-64 that referenced this pull request Sep 26, 2026
The purpose of the `build-installers` SDK artifact is that it offers all
the bits and pieces that are needed to build a full Git for Windows
installer, including everything to build a new `mingw-w64-git` package.

We are about to require Rust to do so, as the Rust parts of Git's source
code will become mandatory with Git v3.0, and Git for Windows will
already enable them after v2.56.

To do so, let's add a workflow_dispatch trigger to the `ci-artifacts`
workflow (which _already_ has everything needed to build the
`build-installers` SDK artifact), accept a git-for-windows/git PR URL to
build from, and then go and build a full installer.

In preparation for git-for-windows/git#6438,
which integrates an incredibly fast, yet still collision-detecting SHA-1
Rust crate, enable DC_SHA1_RS and leave NO_RUST unset when Rust is
installed; otherwise build without Rust. Upload the installer as an
Actions artifact for inspection.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Cherry-picked-from: e604906 (ci-artifacts: offer to exercise the `build-installers` artifact, 2026-09-25)
@dscho

dscho commented Sep 27, 2026

Copy link
Copy Markdown
Member Author

I now repeated this benchmark in a Windows/ARM64 cloud PC, and got this:

SHA1DC backend Median Best of five
C (default) 0m17.926s 0m17.953s
Rust 0.1.2 0m10.707s 0m10.750s
Rust 0.1.3 0m10.737s 0m10.892s

Maybe slightly less pronounced of a speed-up than on Windows/x64, but definitely something to cheer about!

dscho added a commit to git-for-windows/git-sdk-arm64 that referenced this pull request Sep 27, 2026
The purpose of the `build-installers` SDK artifact is that it offers all
the bits and pieces that are needed to build a full Git for Windows
installer, including everything to build a new `mingw-w64-git` package.

We are about to require Rust to do so, as the Rust parts of Git's source
code will become mandatory with Git v3.0, and Git for Windows will
already enable them after v2.56.

To do so, let's add a workflow_dispatch trigger to the `ci-artifacts`
workflow (which _already_ has everything needed to build the
`build-installers` SDK artifact), accept a git-for-windows/git PR URL to
build from, and then go and build a full installer.

In preparation for git-for-windows/git#6438,
which integrates an incredibly fast, yet still collision-detecting SHA-1
Rust crate, enable DC_SHA1_RS and leave NO_RUST unset when Rust is
installed; otherwise build without Rust. Upload the installer as an
Actions artifact for inspection.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Cherry-picked-from: e604906 (ci-artifacts: offer to exercise the `build-installers` artifact, 2026-09-25)
dscho added a commit to dscho/git-sdk-64 that referenced this pull request Sep 27, 2026
The purpose of the `build-installers` SDK artifact is that it offers all
the bits and pieces that are needed to build a full Git for Windows
installer, including everything to build a new `mingw-w64-git` package.

We are about to require Rust to do so, as the Rust parts of Git's source
code will become mandatory with Git v3.0, and Git for Windows will
already enable them after v2.56.

To do so, let's add a workflow_dispatch trigger to the `ci-artifacts`
workflow (which _already_ has everything needed to build the
`build-installers` SDK artifact), accept a git-for-windows/git PR URL to
build from, and then go and build a full installer.

In preparation for git-for-windows/git#6438,
which integrates an incredibly fast, yet still collision-detecting SHA-1
Rust crate, enable DC_SHA1_RS and leave NO_RUST unset when Rust is
installed; otherwise build without Rust. Upload the installer as an
Actions artifact for inspection.

Assisted-by: GPT-6 Sol
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Cherry-picked-from: e604906 (ci-artifacts: offer to exercise the `build-installers` artifact, 2026-09-25)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants