-
Notifications
You must be signed in to change notification settings - Fork 0
Release/1.0.4 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Release/1.0.4 #11
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
dd14d6d
Merge branch 'release/1.0.3' into develop
datacute 1d68c68
next alpha version
datacute ed102bd
Move PackageProjectUrl before its usage
datacute 7e50d9b
Include generator assembly name and version in "auto-generated" comme…
datacute 41ceaac
Restored AddEmbeddedAttributeDefinition Stage
datacute 329d0ca
Improved stage descriptions
datacute 7bd4b19
set global.json to latestFeature
datacute c79c236
Improve the instance cache to skip evaluating hashcode until it is ne…
datacute e9c6931
changelog
datacute ce5e3fa
changelog and release version
datacute 01cefbd
copilot review
datacute File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
149 changes: 149 additions & 0 deletions
149
...s.Content/Datacute/IncrementalGeneratorExtensions/EquatableImmutableArrayInstanceCache.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| // <auto-generated> | ||
| // This file is part of the Datacute.IncrementalGeneratorExtensions package. | ||
| // It is included as a source file and should not be modified. | ||
| // </auto-generated> | ||
|
|
||
| #if !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Threading; | ||
|
|
||
| namespace Datacute.IncrementalGeneratorExtensions | ||
| { | ||
| // The source generation pipelines compare these a lot | ||
| // so being able to quickly tell when they are different | ||
| // is important. | ||
| // We will use an instance cache to find when we can reuse | ||
| // an existing object, massively speeding up the Equals call. | ||
|
|
||
| /// <summary> | ||
| /// A cache for instances of <see cref="EquatableImmutableArray{T}"/>. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of elements in the array, which must implement <see cref="IEquatable{T}"/>.</typeparam> | ||
| public static class EquatableImmutableArrayInstanceCache<T> where T : IEquatable<T> | ||
| { | ||
| // Two-level cache: length -> first element hash -> list of instances | ||
| // Because this is a generic class, there is a separate static cache for each type T | ||
| private static readonly ConcurrentDictionary<int, ConcurrentDictionary<int, List<WeakReference<EquatableImmutableArray<T>>>>> Cache = | ||
| new ConcurrentDictionary<int, ConcurrentDictionary<int, List<WeakReference<EquatableImmutableArray<T>>>>>(); | ||
|
|
||
| /// <summary> | ||
| /// Gets or creates an instance of <see cref="EquatableImmutableArray{T}"/> from the provided values. | ||
| /// </summary> | ||
| /// <param name="values">The immutable array of values to create the instance from.</param> | ||
| /// <param name="cancellationToken">A cancellation token to observe while waiting for the operation to complete.</param> | ||
| /// <returns>An instance of <see cref="EquatableImmutableArray{T}"/> containing the provided values.</returns> | ||
| public static EquatableImmutableArray<T> GetOrCreate(ImmutableArray<T> values, CancellationToken cancellationToken = default) | ||
| { | ||
| if (values.IsEmpty) | ||
| return EquatableImmutableArray<T>.Empty; | ||
|
|
||
| // If we were to calculate the hash of the entire array first, and find | ||
| // matching instances based on that, we would still have to check each element | ||
| // for equality. Instead, we will first find a small number of potentially equal arrays, | ||
| // and then check each element for equality, since that is required anyway. | ||
| // If we find a match, we've saved the time to compute the full hash. | ||
|
|
||
| // To quickly narrow down the candidates for equality checks, | ||
| // this implementation uses a two-level cache: | ||
| // 1. The first level is based on the length of the array. | ||
| // 2. The second level is based on the hash of the first element. | ||
|
|
||
| // TODO: Optimize cache performance for large arrays with localized differences | ||
| // Current approach may be O(n²) when many similar large arrays differ at high indices | ||
| // Potential solutions: | ||
| // 1. Multi-element hashing (hash first few elements for better bucketing) | ||
| // 2. Sparse sampling (hash elements at strategic intervals: start, 1/4, 1/2, 3/4, end) | ||
| // 3. Adaptive difference tracking (learn common difference points, create sub-caches) | ||
| // 4. Hierarchical bucketing (first element -> second element -> etc.) | ||
| // Need real usage data to determine which approach provides best performance | ||
|
|
||
| var comparer = EqualityComparer<T>.Default; | ||
| var length = values.Length; | ||
| var firstElementHash = values[0] == null ? 0 : comparer.GetHashCode(values[0]); | ||
|
|
||
| // Get or create the length-based dictionary | ||
| var lengthDict = Cache.GetOrAdd(length, _ => new ConcurrentDictionary<int, List<WeakReference<EquatableImmutableArray<T>>>>()); | ||
|
|
||
| // Get or create the first-element-hash-based list | ||
| var candidateList = lengthDict.GetOrAdd(firstElementHash, _ => new List<WeakReference<EquatableImmutableArray<T>>>()); | ||
|
|
||
| lock (candidateList) | ||
| { | ||
| // Check for matches (backwards to allow removal of dead references) | ||
| for (int i = candidateList.Count - 1; i >= 0; i--) | ||
| { | ||
| #if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS && !DATACUTE_EXCLUDE_GENERATORSTAGE | ||
| cancellationToken.ThrowIfCancellationRequested(0); | ||
| #else | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| #endif | ||
| if (candidateList[i].TryGetTarget(out var existing)) | ||
| { | ||
| // Check if this candidate matches element by element | ||
| bool isMatch = true; | ||
|
|
||
| for (int elementIndex = 0; elementIndex < length; elementIndex++) | ||
| { | ||
| if (!comparer.Equals(values[elementIndex], existing[elementIndex])) | ||
| { | ||
| isMatch = false; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (isMatch) | ||
| { | ||
| #if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE && !DATACUTE_EXCLUDE_GENERATORSTAGE | ||
| LightweightTrace.IncrementCount(GeneratorStage.EquatableImmutableArrayCacheHit, values.Length); | ||
| #endif | ||
| return existing; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| // Clean up dead references | ||
| #if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE && !DATACUTE_EXCLUDE_GENERATORSTAGE | ||
| LightweightTrace.IncrementCount(GeneratorStage.EquatableImmutableArrayCacheWeakReferenceRemoved, values.Length); | ||
| LightweightTrace.DecrementCount(GeneratorStage.EquatableImmutableArrayLength, values.Length); | ||
| #endif | ||
| candidateList.RemoveAt(i); | ||
| } | ||
| } | ||
|
|
||
| // No match found, calculate hash and create new instance | ||
| var hash = CalculateHashCode(values, comparer, firstElementHash, 1); | ||
| #if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE && !DATACUTE_EXCLUDE_GENERATORSTAGE | ||
| // Record a histogram of the array sizes we are being asked to create | ||
| LightweightTrace.IncrementCount(GeneratorStage.EquatableImmutableArrayCacheMiss, values.Length); | ||
| LightweightTrace.IncrementCount(GeneratorStage.EquatableImmutableArrayLength, values.Length); | ||
| #endif | ||
| var newResult = new EquatableImmutableArray<T>(values, hash); | ||
| candidateList.Add(new WeakReference<EquatableImmutableArray<T>>(newResult)); | ||
| return newResult; | ||
| } | ||
| } | ||
|
|
||
| private static int CalculateHashCode(ImmutableArray<T> values, EqualityComparer<T> comparer, int currentHash, int hashedValues) | ||
| { | ||
| int hash = currentHash; | ||
| for (var index = hashedValues; index < values.Length; index++) | ||
| { | ||
| var value = values[index]; | ||
| hash = HashHelpers_Combine(hash, value == null ? 0 : comparer.GetHashCode(value)); | ||
| } | ||
| return hash; | ||
| } | ||
|
|
||
| private static int HashHelpers_Combine(int h1, int h2) | ||
| { | ||
| // RyuJIT optimizes this to use the ROL instruction | ||
| // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 | ||
| uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); | ||
| return ((int)rol5 + h1) ^ h2; | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| { | ||
| "sdk": { | ||
| "version": "9.0.0", | ||
| "rollForward": "latestMajor", | ||
| "allowPrerelease": true | ||
| "rollForward": "latestFeature" | ||
| } | ||
| } |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.