Repository navigation
Improve Perf of MemoryFileSystem.EnumerateItems - #110
Merged
Merged
Conversation
EnumerateItemsImpl combined each child with `directoryPath / name`, which validates and normalizes the name and then the whole combined path again, and converted the creation, last write and last access times of every child to DateTimeOffset, each conversion looking up the UTC offset of the local time zone. - Combine a child path by concatenation: the name of a child node is always a single part of a validated path and the directory path is absolute and normalized, so the result is already normalized and is the same path. - Convert identical times only once. The times are compared on their full binary representation, as the conversion also depends on the Kind (and on the daylight saving flag of an ambiguous local time) which DateTime equality ignores, so the enumerated values are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lahma
added a commit
to lahma/test262-harness-dotnet
that referenced
this pull request
Oct 20, 2026
0.25.0 contains only our three upstream changes to MemoryFileSystem: a lock-free fast path for node locks (xoofx/zio#109), faster EnumerateItems (xoofx/zio#110) and the fix for a deadlock between a rename inside a sub-directory and concurrent lookups (xoofx/zio#111). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
For every child of every visited directory,
MemoryFileSystem.EnumerateItemsImplbuilt the child path withdirectoryPath / nodePair.Key. That converts the name to aUPath, which validates and normalizes it, then concatenates, and validates and normalizes the whole combined path again. For the children of/, the combined//nameeven takes the slow normalization path and allocates a second string.It also assigned the three
DateTimetimes of each node to theDateTimeOffsetfields ofFileSystemItem. The nodes store local times, so each assignment looks up the UTC offset of the local time zone at that time: three conversions per item.Changes
CombineChildPath), which gives exactly the same path asdirectoryPath / name: the name of a child node is always a single part of a validated path (no separator, not.or..), and the directory path is absolute and normalized. The concatenation is therefore already normalized and is built with the internalnew UPath(string, safe: true). A relative directory path (not expected) falls back todirectoryPath / name.DateTime ==: equality only compares ticks, while the conversion also depends on theKindand on the daylight-saving flag of an ambiguous local time. So every enumeratedCreationTime,LastWriteTimeandLastAccessTimeis the same value as before.The async counterpart (
MemoryFileSystemAsync.gen.cs) is regenerated withZio.AsyncCodeGen.EnumeratePathsImplis deliberately left as it is. I also tried computing the child path there only when the child is returned or followed: that made a selective pattern 43% faster, but consistently made an*enumeration slower in my runs, so it isn't part of this PR.Tests
TestMemoryFileSystemEnumeration.TestEnumeratedPathsAreNormalized: paths and items of a tree with children of/, nested children, and names with dots (.hidden,x..y,.z.) and spaces; each path equalsUPath.Combine(parent, name)and is already normalized; also search patterns with a directory part.TestMemoryFileSystemEnumeration.TestEnumeratedItemTimesMatchTheFileSystemTimes: each item timeEqualsExacttheDateTimeOffsetof the correspondingGet*Timevalue, for a directory (all times equal), a written file, a read file (all times different), the same ticks with differentKinds, and explicitly set times.TestAsyncFileSystem.TestMemoryFileSystemAsyncEnumeratesNormalizedPathsAndTimes: the same check forMemoryFileSystemAsync.All tests pass on net10.0 and net472.
Benchmarks
A
MemoryFileSystemof 50,000 files in 1,051 directories (/test/suite-XX/group-YY/test-case-ZZZ.js):EnumerateItems("/", AllDirectories)The ranges come from four interleaved runs of each. Allocation is unchanged (5.08 MB). BenchmarkDotNet 0.15.8 default job, .NET 10, AMD Ryzen 9 5950X, pinned to one core.
🤖 Generated with Claude Code