From 6840639e177de5eba5c718545eb1af84b5ee77b1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:08:46 -0700 Subject: [PATCH 1/9] Add builder --- .../Core/CompilerExtensions.projitems | 1 + .../Core/Utilities/FixedSizeArrayBuilder.cs | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index bbe77051ecf65..ac76082e5e8e3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -497,6 +497,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs new file mode 100644 index 0000000000000..4b61967d943a3 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +internal sealed class FixedSizeArrayBuilder +{ + private static readonly ObjectPool> s_pool = new(() => new()); + + private T[] _values = Array.Empty(); + private int _index; + + private FixedSizeArrayBuilder() + { + } + + public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) + { + builder = s_pool.Allocate(); + Contract.ThrowIfTrue(builder._values != Array.Empty()); + Contract.ThrowIfTrue(builder._index != 0); + builder._values = new T[capacity]; + + return new(builder); + } + + public void Add(T value) + => _values[_index++] = value; + + public T this[int index] + { + get => _values[index]; + set => _values[index] = value; + } + + public ImmutableArray MoveToImmutable() + { + Contract.ThrowIfTrue(_index != _values.Length); + var result = ImmutableCollectionsMarshal.AsImmutableArray(_values); + _values = Array.Empty(); + _index = 0; + return result; + } + + public struct PooledFixedSizeArrayBuilder(FixedSizeArrayBuilder builder) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + Contract.ThrowIfTrue(_disposed); + _disposed = true; + + // Put the builder back in the pool. If we were in the middle of creating hte array, but never moved it + // out, this will leak the array (can happen during things like cancellation). That's acceptable as that + // should not be the mainline path. And, in that event, it's not like we can use that array anyways as it + // won't be the right size for the next caller that needs a FixedSizeArrayBuilder of a different size. + builder._values = Array.Empty(); + builder._index = 0; + s_pool.Free(builder); + } + } +} From 37f328154051a9eecb6a870095d0d8f91c34d42b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:13:23 -0700 Subject: [PATCH 2/9] Update code --- .../Workspace/Solution/StateChecksums.cs | 7 +++-- .../Workspace/Solution/TextDocumentStates.cs | 27 +++++++++---------- .../Remote/Core/AbstractAssetProvider.cs | 7 +++-- .../RemoteSourceGenerationService.cs | 14 +++++----- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2b3138742a0e0..cfba1aa4da307 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -533,12 +533,11 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - var checksums = new Checksum[references.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(references.Count, out var checksums); foreach (var reference in references) - checksums[index++] = tuple.serializer.CreateChecksum(reference, tuple.cancellationToken); + checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); - return new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(checksums)); + return new ChecksumCollection(checksums.MoveToImmutable()); }, (serializer, cancellationToken)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 21e522eb393c7..392c914c438f0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -117,12 +117,11 @@ public ImmutableArray SelectAsArray(Func selecto public ImmutableArray SelectAsArray(Func selector, TArg arg) { - var result = new TValue[_map.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(_map.Count, out var result); foreach (var (_, state) in _map) - result[index++] = selector(state, arg); + result.Add(selector(state, arg)); - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); } public TextDocumentStates AddRange(ImmutableArray states) @@ -272,24 +271,22 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var attributeChecksums = new Checksum[_map.Count]; - var textChecksums = new Checksum[_map.Count]; - var documentIds = new DocumentId[_map.Count]; + using var _1 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); + using var _2 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var textChecksums); + using var _3 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var documentIds); - var index = 0; foreach (var (documentId, state) in _map) { var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - attributeChecksums[index] = stateChecksums.Info; - textChecksums[index] = stateChecksums.Text; - documentIds[index] = documentId; - index++; + attributeChecksums.Add(stateChecksums.Info); + textChecksums.Add(stateChecksums.Text); + documentIds.Add(documentId); } return new( - new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(attributeChecksums)), - new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(textChecksums)), - ImmutableCollectionsMarshal.AsImmutableArray(documentIds)); + new ChecksumCollection(attributeChecksums.MoveToImmutable()), + new ChecksumCollection(textChecksums.MoveToImmutable()), + documentIds.MoveToImmutable()); } public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 652ad566b191a..f2c0ba6458849 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -95,16 +95,15 @@ await analyzerConfigDocumentInfosTask.ConfigureAwait(false), async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - var documentInfos = new DocumentInfo[checksumsAndIds.Length]; + using var _ = FixedSizeArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); - var index = 0; foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); - documentInfos[index++] = await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false); + documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false)); } - return ImmutableCollectionsMarshal.AsImmutableArray(documentInfos); + return documentInfos.MoveToImmutable(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 6d7e72d98ef17..1d2fe14ba42b6 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -38,15 +38,14 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - var result = new (SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)[documentStates.Ids.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result[index++] = (state.Identity, state.GetContentIdentity(), state.GenerationDateTime); + result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); } - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); }, cancellationToken); } @@ -58,17 +57,16 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - var result = new string[documentIds.Length]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(documentIds.Length, out var result); foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); var state = documentStates.GetRequiredState(id); var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); - result[index++] = text.ToString(); + result.Add(text.ToString()); } - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); }, cancellationToken); } From 2a1a500a61f61aee71e59747da48dc3b233b2262 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:15:12 -0700 Subject: [PATCH 3/9] More cases --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index f2c0ba6458849..bff20c2b845ac 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -196,7 +196,7 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - var builder = ImmutableArray.CreateBuilder(checksumSet.Count); + using var _ = FixedSizeArrayBuilder.GetInstance(checksumSet.Count, out var builder); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, From b9d7595acdbfa40dc56c5acf5b41003a9c4031a4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:18:21 -0700 Subject: [PATCH 4/9] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 4b61967d943a3..0aba4afbaa7d4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -8,6 +8,13 @@ using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; +/// +/// A bare-bones, pooled builder, focused on the case of producing s where the final +/// array size is known at construction time. In the golden path, where all the expected items are added to the +/// builder, and is called, this type is entirely garbage free. In the non-golden path +/// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary +/// array created to store the results. +/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); From 704f683917982e5d1c0b27f15ed07e1dbdd6ced3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:19:35 -0700 Subject: [PATCH 5/9] Remove indexer --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 0aba4afbaa7d4..22f795d4b8507 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,6 +15,11 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// +/// +/// It is an error for a client of this type to specify a capacity and then attempt to call without that number of elements actually having been added to the builder. This will throw +/// if attempted. +/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); @@ -39,12 +44,6 @@ public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSiz public void Add(T value) => _values[_index++] = value; - public T this[int index] - { - get => _values[index]; - set => _values[index] = value; - } - public ImmutableArray MoveToImmutable() { Contract.ThrowIfTrue(_index != _values.Length); From 463a4c37a618d5da03a760c15a7432df036706cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:21:12 -0700 Subject: [PATCH 6/9] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 22f795d4b8507..071db1b435a3d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,11 +15,6 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// -/// -/// It is an error for a client of this type to specify a capacity and then attempt to call without that number of elements actually having been added to the builder. This will throw -/// if attempted. -/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); @@ -44,6 +39,13 @@ public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSiz public void Add(T value) => _values[_index++] = value; + /// + /// Moves the underlying buffer out of control of this type, into the returned . It + /// is an error for a client of this type to specify a capacity and then attempt to call without that number of elements actually having been added to the builder. This will + /// throw if attempted. This is effectively unusable once this is called. + /// The internal buffer will reset to an empty array, meaning no more items could ever be added to it. + /// public ImmutableArray MoveToImmutable() { Contract.ThrowIfTrue(_index != _values.Length); From aa57c848843042beff5aa2b932c580ca1248b7ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:22:10 -0700 Subject: [PATCH 7/9] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 071db1b435a3d..a1db353fbc08f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -26,6 +26,11 @@ private FixedSizeArrayBuilder() { } + /// + /// Gets a builder which wraps an array whose length is exactly . This array can only be + /// moved into a through , and only when exactly that + /// number of elements have been added. + /// public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) { builder = s_pool.Allocate(); From a30736aaff8d258411524de0a594c66f6d899545 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:31:29 -0700 Subject: [PATCH 8/9] Switch to non-copyable struct --- .../Workspace/Solution/StateChecksums.cs | 2 +- .../Workspace/Solution/TextDocumentStates.cs | 8 ++-- .../Remote/Core/AbstractAssetProvider.cs | 6 +-- .../RemoteSourceGenerationService.cs | 4 +- .../Core/Utilities/FixedSizeArrayBuilder.cs | 45 ++----------------- 5 files changed, 13 insertions(+), 52 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index cfba1aa4da307..7762b7c92f61f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -533,7 +533,7 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - using var _ = FixedSizeArrayBuilder.GetInstance(references.Count, out var checksums); + var checksums = new FixedSizeArrayBuilder(references.Count); foreach (var reference in references) checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 392c914c438f0..d67a3608d5ebe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -117,7 +117,7 @@ public ImmutableArray SelectAsArray(Func selecto public ImmutableArray SelectAsArray(Func selector, TArg arg) { - using var _ = FixedSizeArrayBuilder.GetInstance(_map.Count, out var result); + var result = new FixedSizeArrayBuilder(_map.Count); foreach (var (_, state) in _map) result.Add(selector(state, arg)); @@ -271,9 +271,9 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - using var _1 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); - using var _2 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var textChecksums); - using var _3 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var documentIds); + var attributeChecksums = new FixedSizeArrayBuilder(_map.Count); + var textChecksums = new FixedSizeArrayBuilder(_map.Count); + var documentIds = new FixedSizeArrayBuilder(_map.Count); foreach (var (documentId, state) in _map) { diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index bff20c2b845ac..fbbd5ec9a74ca 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -95,7 +95,7 @@ await analyzerConfigDocumentInfosTask.ConfigureAwait(false), async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - using var _ = FixedSizeArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + var documentInfos = new FixedSizeArrayBuilder(checksumsAndIds.Length); foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { @@ -196,7 +196,7 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - using var _ = FixedSizeArrayBuilder.GetInstance(checksumSet.Count, out var builder); + using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, @@ -204,6 +204,6 @@ await assetProvider.GetAssetHelper().GetAssetsAsync( builder, cancellationToken).ConfigureAwait(false); - return builder.MoveToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 1d2fe14ba42b6..58e9194da495a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -38,7 +38,7 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); + var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.Ids.Count); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); @@ -57,7 +57,7 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = FixedSizeArrayBuilder.GetInstance(documentIds.Length, out var result); + var result = new FixedSizeArrayBuilder(documentIds.Length); foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index a1db353fbc08f..89e1109e4fe50 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,32 +15,12 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// -internal sealed class FixedSizeArrayBuilder +[NonCopyable] +internal struct FixedSizeArrayBuilder(int capacity) { - private static readonly ObjectPool> s_pool = new(() => new()); - - private T[] _values = Array.Empty(); + private T[] _values = new T[capacity]; private int _index; - private FixedSizeArrayBuilder() - { - } - - /// - /// Gets a builder which wraps an array whose length is exactly . This array can only be - /// moved into a through , and only when exactly that - /// number of elements have been added. - /// - public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) - { - builder = s_pool.Allocate(); - Contract.ThrowIfTrue(builder._values != Array.Empty()); - Contract.ThrowIfTrue(builder._index != 0); - builder._values = new T[capacity]; - - return new(builder); - } - public void Add(T value) => _values[_index++] = value; @@ -59,23 +39,4 @@ public ImmutableArray MoveToImmutable() _index = 0; return result; } - - public struct PooledFixedSizeArrayBuilder(FixedSizeArrayBuilder builder) : IDisposable - { - private bool _disposed; - - public void Dispose() - { - Contract.ThrowIfTrue(_disposed); - _disposed = true; - - // Put the builder back in the pool. If we were in the middle of creating hte array, but never moved it - // out, this will leak the array (can happen during things like cancellation). That's acceptable as that - // should not be the mainline path. And, in that event, it's not like we can use that array anyways as it - // won't be the right size for the next caller that needs a FixedSizeArrayBuilder of a different size. - builder._values = Array.Empty(); - builder._index = 0; - s_pool.Free(builder); - } - } } From f89b813a22d106ceabfc853264369d979f81b18c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:32:50 -0700 Subject: [PATCH 9/9] Add capacity --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index fbbd5ec9a74ca..6a3c7c067f3ad 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -194,6 +194,9 @@ public static async Task> GetAssetsArrayAsync( this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { using var _1 = PooledHashSet.GetInstance(out var checksumSet); +#if NET + checksumSet.EnsureCapacity(checksums.Children.Length); +#endif checksumSet.AddAll(checksums.Children); using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder);