Repository navigation
Reduce tar extraction overhead and improve async archive creation - #135357
Open
JeremyKuhne wants to merge 3 commits into
Open
JeremyKuhne wants to merge 3 commits into
JeremyKuhne wants to merge 3 commits into
Conversation
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-formats-tar |
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Security-sensitive cross-platform path handling changed substantially, and macOS execution was not validated.
0 open findings
What changed in this PR
Optimizes tar extraction and file-backed archive creation while preserving extraction security and metadata behavior.
Changes:
- Reduces path-resolution calls and per-entry allocations.
- Improves buffering, async flushing, timestamps, and sequential reads.
- Adds cross-platform regression and cancellation coverage.
| File | Description |
|---|---|
TarFile.ExtractToDirectory.Stream.Tests.cs |
Covers empty roots and late links. |
TarFile.ExtractToDirectory.File.Tests.Windows.cs |
Tests Windows reparse-root handling. |
TarFile.ExtractToDirectory.File.Tests.Unix.cs |
Tests Unix links and permissions. |
TarFile.ExtractToDirectory.File.Tests.cs |
Adds root-retargeting test support. |
TarEntry.ExtractToFile.Tests.cs |
Tests cancellation and timestamps. |
TarWriter.Windows.cs |
Adds sequential-read hints. |
TarFile.cs |
Adds output buffering and extraction context. |
TarEntry.Windows.cs |
Optimizes Windows path resolution. |
TarEntry.Unix.cs |
Optimizes Unix path resolution. |
TarEntry.cs |
Reworks containment checks and file extraction. |
System.Formats.Tar.csproj |
Includes required Windows interop sources. |
🧠 Review effort: Balanced
This was referenced Oct 8, 2026
Closed
Rebuild the optimizations on main including dotnet#135748. Preserve deferred symlink validation and fresh descendant checks while lazily reusing ordinary roots for hard-link targets and avoiding path-probe allocations. Reuse output handles for timestamps, forward flush cancellation, and right-size tiny-file buffers. Use 32 KiB owned archive output buffers after the measured size sweep, retaining Windows sequential-read hints. Add timestamp, cancellation, descendant mutation, and linked-root retargeting coverage, including Windows junctions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JeremyKuhne
force-pushed
the
perf/tar-filesystem-overhead
branch
from
October 19, 2026 23:50
2fcafa1 to
513775d
Compare
Prime the ordinary-root cache with a successful hard link before changing the descendant directory, then verify the initial hard link keeps its contents. Both sync and async cases detect a generated cache-bypass mutant and pass against the normal product on Windows and Linux. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rzikm
reviewed
Oct 21, 2026
rzikm
left a comment
Member
There was a problem hiding this comment.
Seems reasonable in general, I left couple of comments
Member
|
@JeremyKuhne have you had time to take a look at my comments? |
Member
Author
@rzikm Sorry, missed the notification- thanks for the heads up I'll look today! :) |
Disable source-file buffering on Windows and Unix, cache trusted extraction roots once, and replace concurrent-mutation tests with static linked-root coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Rebuilt on main at
699100ec729dddc77dcafa770b3b9e177357ec41, including #135748 (df0dd45e1302190770b48ce9676d237224f8d2e3). The measurements below compare that updated main baseline with this PR, not with the implementation before #135748.This version builds on the new symlink-validation design instead of restoring the earlier PR's empty-destination descendant-check bypass. No public API is added.
Changes
Performance beyond #135748
Fresh BenchmarkDotNet Release measurements on Windows 11 / Ryzen 7 9700X and WSL2 Ubuntu on native ext4. Baseline and PR use the same locally built 12.0.0-dev runtime; only the platform Tar assembly differs. All 60 benchmark rows completed with populated time/allocation results and verified loaded-assembly hashes. Every output is checked outside timing for contents, populations, timestamps under the frozen validation policy, and applicable hard-link identities/groups.
Cells are sync / async. Times are milliseconds; negative percentages mean less time or cumulative managed allocation per archive, not lower peak memory.
SDK extraction allocation falls 27.57 / 32.39 -> 20.39 / 24.88 MiB on Windows and 24.96 / 27.70 -> 17.93 / 20.45 MiB on Linux. The real archives are the pinned .NET 11 RC SDK (~726 MB payload) and Runtime (~87 MB); creation repacks their verified trees as uncompressed tar. Small is 32 256-byte files, Tiny is 4,096 256-byte files, Mixed is 256 files totaling ~17 MiB, and Large is four 16 MiB files.
These are observed means, not universal latency guarantees. In particular, Runtime tar sync extraction's +3.5% mean has substantial dispersion in the PR run (44.7 ms standard deviation), and the short Large controls are effectively flat. The fixed creation buffer adds 28 KiB per synchronous archive; that explains the large relative allocation percentages for inexpensive creation cases. Linux's modest elapsed-time changes should not be overinterpreted.
Windows extraction uses seven measured iterations, ColdStart, zero BDN warmups; real extraction has one setup extraction/verification. Real creation uses ten measured iterations and one setup creation. Synthetic creation uses 15 measured iterations and three warmups. Linux SDK extraction uses ten measured iterations and one setup extraction. The OS file cache is warm, storage caches were not flushed, and platform runs were sequential. Short-iteration BDN warnings remain applicable. These are JIT measurements, not NativeAOT or macOS measurements.
Why 32 KiB rather than 64 KiB or larger?
The current implementation was swept at 4, 8, 16, 32, 64, 128, 256, 512, 1024, and 2048 KiB. A 100-row BDN screen was followed by 693 independently verified, rotating/reversed-order observations, excluding 20 primers per arm/case. The warm paired results are:
32 KiB captures most of the tiny-file benefit with a smaller per-archive allocation. Larger buffers can further improve SDK and large-file throughput, but 128 KiB and above put their arrays on the large object heap. This PR chooses the smaller default rather than maximizing every large-archive result. Callers prioritizing that throughput can supply their own more heavily buffered stream.
Linux SDK creation is not included: the strict oracle rejects five omitted SDK dotfiles under the existing enumeration behavior. No product or oracle change was made to accept that omission. Linux buffer-size comparisons use the verified synthetic workloads instead.
Validation and limits
Passed on Windows, with analyzers enabled:
Passed on Linux, with analyzers enabled:
The root cache assumes that ordinary roots and their ancestors are not replaced externally during extraction; it is not handle-anchored namespace-race protection. Descendants are still checked freshly, and linked roots/ancestors are not cached. No macOS execution or new NativeAOT measurement is claimed.
Related to #135128.
Note
This description was generated with GitHub Copilot.