| File: StringPool.cs | Web Access |
| Project: Microsoft.NET.ProjectData.csproj (Microsoft.NET.ProjectData) |
// 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.Collections.Concurrent; using System.Runtime.CompilerServices; namespace Microsoft.NET.ProjectData; /// <summary> /// A thread-safe string deduplication pool. Ensures that equal strings share a single /// object instance, reducing memory when many projects carry identical paths (SDK refs, /// analyzer paths, NuGet packages, capabilities, property keys, etc.). /// /// <para>Internally uses striped <see cref="ConcurrentDictionary{TKey, TValue}"/>s so /// concurrent readers and writers experience minimal contention. Striping improves /// cache locality across the 16 independent buckets.</para> /// /// <para>The pool is intended to live as long as the data it deduplicates (e.g. scoped to /// the <c>DataModelSharedState</c> lifetime). It is not self-evicting — entries stay until /// the pool is collected.</para> /// </summary> public sealed class StringPool { private const int StripeCount = 16; // power of two for fast masking private const int StripeMask = StripeCount - 1; private readonly ConcurrentDictionary<string, string>[] stripes; private readonly Action? onGetOrAdd; public StringPool() : this(onGetOrAdd: null) { } internal StringPool(Action? onGetOrAdd) { this.onGetOrAdd = onGetOrAdd; this.stripes = new ConcurrentDictionary<string, string>[StripeCount]; for (int i = 0; i < StripeCount; i++) this.stripes[i] = new(StringComparer.Ordinal); } /// <summary> /// Returns the canonical instance of <paramref name="value"/>. If an equal string /// is already in the pool, the pooled instance is returned and <paramref name="value"/> /// becomes eligible for GC. Otherwise <paramref name="value"/> is added to the pool /// and returned as-is. /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetOrAdd(string value) { this.onGetOrAdd?.Invoke(); int stripe = value.GetHashCode() & StripeMask; return this.stripes[stripe].GetOrAdd(value, static v => v); } }