diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index 1f773903ed9..a554ecfd951 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -357,10 +357,6 @@
-
-
-
-
Collections\CopyOnWriteDictionary.cs
diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj
index a75bbeff256..98f08298b0b 100644
--- a/src/MSBuild/MSBuild.csproj
+++ b/src/MSBuild/MSBuild.csproj
@@ -133,10 +133,6 @@
-
-
-
-
diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj
index 901c6a53a8e..0437fdcbb36 100644
--- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj
+++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj
@@ -129,15 +129,6 @@
NodeShutdown.cs
-
- IInternable.cs
-
-
- WeakStringCache.cs
-
-
- OpportunisticIntern.cs
-
ReadOnlyEmptyCollection.cs
@@ -169,7 +160,6 @@
-
LogMessagePacket.cs
diff --git a/src/MSBuildTaskHost/WeakStringCache.Locking.cs b/src/MSBuildTaskHost/WeakStringCache.Locking.cs
deleted file mode 100644
index d26dd984963..00000000000
--- a/src/MSBuildTaskHost/WeakStringCache.Locking.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.Build
-{
- ///
- /// Implements the WeakStringCache functionality on .NET Framework 3.5 where ConcurrentDictionary is not available.
- ///
- internal sealed partial class WeakStringCache : IDisposable
- {
- private readonly Dictionary _stringsByHashCode;
-
- public WeakStringCache()
- {
- _stringsByHashCode = new Dictionary(_initialCapacity);
- }
-
- ///
- /// Main entrypoint of this cache. Tries to look up a string that matches the given internable. If it succeeds, returns
- /// the string and sets cacheHit to true. If the string is not found, calls ExpensiveConvertToString on the internable,
- /// adds the resulting string to the cache, and returns it, setting cacheHit to false.
- ///
- /// The internable describing the string we're looking for.
- /// A string matching the given internable.
- public string GetOrCreateEntry(T internable, out bool cacheHit) where T : IInternable
- {
- int hashCode = GetInternableHashCode(internable);
-
- StringWeakHandle handle;
- string result;
- bool addingNewHandle = false;
-
- lock (_stringsByHashCode)
- {
- if (_stringsByHashCode.TryGetValue(hashCode, out handle))
- {
- result = handle.GetString(internable);
- if (result != null)
- {
- cacheHit = true;
- return result;
- }
- }
- else
- {
- handle = new StringWeakHandle();
- addingNewHandle = true;
- }
-
- // We don't have the string in the cache - create it.
- result = internable.ExpensiveConvertToString();
-
- // Set the handle to reference the new string.
- handle.SetString(result);
-
- if (addingNewHandle)
- {
- // Prevent the dictionary from growing forever with GC handles that don't reference live strings anymore.
- if (_stringsByHashCode.Count >= _scavengeThreshold)
- {
- // Get rid of unused handles.
- ScavengeNoLock();
- // And do this again when the number of handles reaches double the current after-scavenge number.
- _scavengeThreshold = _stringsByHashCode.Count * 2;
- }
- }
- _stringsByHashCode[hashCode] = handle;
- }
-
- cacheHit = false;
- return result;
- }
-
- ///
- /// Iterates over the cache and removes unused GC handles, i.e. handles that don't reference live strings.
- /// This is expensive so try to call such that the cost is amortized to O(1) per GetOrCreateEntry() invocation.
- /// Assumes the lock is taken by the caller.
- ///
- private void ScavengeNoLock()
- {
- List keysToRemove = null;
- foreach (KeyValuePair entry in _stringsByHashCode)
- {
- if (!entry.Value.IsUsed)
- {
- entry.Value.Free();
- keysToRemove ??= new List();
- keysToRemove.Add(entry.Key);
- }
- }
- if (keysToRemove != null)
- {
- for (int i = 0; i < keysToRemove.Count; i++)
- {
- _stringsByHashCode.Remove(keysToRemove[i]);
- }
- }
- }
-
- ///
- /// Public version of ScavengeUnderLock() which takes the lock.
- ///
- public void Scavenge()
- {
- lock (_stringsByHashCode)
- {
- ScavengeNoLock();
- }
- }
-
- ///
- /// Returns internal debug counters calculated based on the current state of the cache.
- ///
- public DebugInfo GetDebugInfo()
- {
- lock (_stringsByHashCode)
- {
- return GetDebugInfoImpl();
- }
- }
- }
-}
diff --git a/src/Shared/Constants.cs b/src/Shared/Constants.cs
index 42f82771737..d8b2c66c98d 100644
--- a/src/Shared/Constants.cs
+++ b/src/Shared/Constants.cs
@@ -60,8 +60,6 @@ internal static class MSBuildConstants
///
internal const string CurrentToolsVersion = "Current";
- // if you change the key also change the following clones
- // Microsoft.Build.OpportunisticIntern.BucketedPrioritizedStringList.TryIntern
internal const string MSBuildDummyGlobalPropertyHeader = "MSBuildProjectInstance";
///
diff --git a/src/Shared/IInternable.cs b/src/Shared/IInternable.cs
deleted file mode 100644
index 9bfa741e097..00000000000
--- a/src/Shared/IInternable.cs
+++ /dev/null
@@ -1,341 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Text;
-using Microsoft.Build.Shared;
-
-namespace Microsoft.Build
-{
- #region IInternable
- ///
- /// Define the methods needed to intern something.
- ///
- internal interface IInternable
- {
- ///
- /// The length of the target.
- ///
- int Length { get; }
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- char this[int index] { get; }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- string ExpensiveConvertToString();
-
- ///
- /// Compare target to string. Assumes string is of equal or smaller length than target.
- ///
- bool StartsWithStringByOrdinalComparison(string other);
-
- ///
- /// Reference compare target to string. If target is non-string this should return false.
- ///
- bool ReferenceEquals(string other);
- }
- #endregion
-
-
- #region IInternable Implementations
- ///
- /// A wrapper over StringBuilder.
- ///
- internal readonly struct StringBuilderInternTarget : IInternable
- {
- ///
- /// The held StringBuilder
- ///
- private readonly StringBuilder _target;
-
- ///
- /// Pointless comment about constructor.
- ///
- internal StringBuilderInternTarget(StringBuilder target)
- {
- _target = target;
- }
-
- ///
- /// The length of the target.
- ///
- public int Length => _target.Length;
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- public char this[int index] => _target[index];
-
- ///
- /// Never reference equals to string.
- ///
- public bool ReferenceEquals(string other) => false;
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public string ExpensiveConvertToString()
- {
- // PERF NOTE: This will be an allocation hot-spot because the StringBuilder is finally determined to
- // not be internable. There is still only one conversion of StringBuilder into string it has just
- // moved into this single spot.
- return _target.ToString();
- }
-
- ///
- /// Compare target to string. Assumes string is of equal or smaller length than target.
- ///
- public bool StartsWithStringByOrdinalComparison(string other)
- {
-#if DEBUG
- ErrorUtilities.VerifyThrow(other.Length <= _target.Length, "should be at most as long as target");
-#endif
- int length = other.Length;
-
- // Backwards because the end of the string is more likely to be different earlier in the loop.
- // For example, C:\project1, C:\project2
- for (int i = length - 1; i >= 0; --i)
- {
- if (_target[i] != other[i])
- {
- return false;
- }
- }
-
- return true;
- }
-
- ///
- /// Don't use this function. Use ExpensiveConvertToString
- ///
- public override string ToString() => throw new InvalidOperationException();
- }
-
- ///
- /// A wrapper over char[].
- ///
- internal readonly struct CharArrayInternTarget : IInternable
- {
- ///
- /// Start index for the string
- ///
- private readonly int _startIndex;
-
- ///
- /// The held array
- ///
- private readonly char[] _target;
-
- ///
- /// Pointless comment about constructor.
- ///
- internal CharArrayInternTarget(char[] target, int count)
- : this(target, 0, count)
- {
- }
-
- ///
- /// Pointless comment about constructor.
- ///
- internal CharArrayInternTarget(char[] target, int startIndex, int count)
- {
-#if DEBUG
- if (startIndex + count > target.Length)
- {
- ErrorUtilities.ThrowInternalError("wrong length");
- }
-#endif
- _target = target;
- _startIndex = startIndex;
- Length = count;
- }
-
- ///
- /// The length of the target.
- ///
- public int Length { get; }
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- public char this[int index]
- {
- get
- {
- return _target[index + _startIndex];
- }
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public bool ReferenceEquals(string other)
- {
- return false;
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public string ExpensiveConvertToString()
- {
- // PERF NOTE: This will be an allocation hot-spot because the char[] is finally determined to
- // not be internable. There is still only one conversion of char[] into string it has just
- // moved into this single spot.
- return new string(_target, _startIndex, Length);
- }
-
- ///
- /// Compare target to string. Assumes string is of equal or smaller length than target.
- ///
- public bool StartsWithStringByOrdinalComparison(string other)
- {
-#if DEBUG
- ErrorUtilities.VerifyThrow(other.Length <= Length, "should be at most as long as target");
-#endif
- // Backwards because the end of the string is (by observation of Australian Government build) more likely to be different earlier in the loop.
- // For example, C:\project1, C:\project2
- for (int i = other.Length - 1; i >= 0; --i)
- {
- if (_target[i + _startIndex] != other[i])
- {
- return false;
- }
- }
-
- return true;
- }
-
- ///
- /// Don't use this function. Use ExpensiveConvertToString
- ///
- public override string ToString()
- {
- throw new InvalidOperationException();
- }
- }
-
- ///
- /// Wrapper over a string.
- ///
- internal readonly struct StringInternTarget : IInternable
- {
- ///
- /// Stores the wrapped string.
- ///
- private readonly string _target;
-
- ///
- /// Constructor of the class
- ///
- /// The string to wrap
- internal StringInternTarget(string target)
- {
- ErrorUtilities.VerifyThrowArgumentLength(target, nameof(target));
- _target = target;
- }
-
- ///
- /// Gets the length of the target string.
- ///
- public int Length => _target.Length;
-
- ///
- /// Gets the n character in the target string.
- ///
- /// Index of the character to gather.
- /// The character in the position marked by index.
- public char this[int index] => _target[index];
-
- ///
- /// Returns the target which is already a string.
- ///
- /// The target string.
- public string ExpensiveConvertToString() => _target;
-
- ///
- /// Compare target to string. Assumes string is of equal or smaller length than target.
- ///
- /// The string to compare with the target.
- /// True if target starts with , false otherwise.
- public bool StartsWithStringByOrdinalComparison(string other) => _target.StartsWith(other, StringComparison.Ordinal);
-
- ///
- /// Verifies if the reference of the target string is the same of the given string.
- ///
- /// The string reference to compare to.
- /// True if both references are equal, false otherwise.
- public bool ReferenceEquals(string other) => ReferenceEquals(_target, other);
- }
-
- ///
- /// Wrapper over a substring of a string.
- ///
- internal readonly struct SubstringInternTarget : IInternable
- {
- ///
- /// Stores the wrapped string.
- ///
- private readonly string _target;
-
- ///
- /// Start index of the substring within the wrapped string.
- ///
- private readonly int _startIndex;
-
- ///
- /// Constructor of the class
- ///
- /// The string to wrap.
- /// Start index of the substring within .
- /// Length of the substring.
- internal SubstringInternTarget(string target, int startIndex, int length)
- {
-#if DEBUG
- if (startIndex + length > target.Length)
- {
- ErrorUtilities.ThrowInternalError("wrong length");
- }
-#endif
- _target = target;
- _startIndex = startIndex;
- Length = length;
- }
-
- ///
- /// Gets the length of the target substring.
- ///
- public int Length { get; }
-
- ///
- /// Gets the n character in the target substring.
- ///
- /// Index of the character to gather.
- /// The character in the position marked by index.
- public char this[int index] => _target[index + _startIndex];
-
- ///
- /// Returns the target substring as a string.
- ///
- /// The substring.
- public string ExpensiveConvertToString() => _target.Substring(_startIndex, Length);
-
- ///
- /// Compare target substring to a string. Assumes string is of equal or smaller length than the target substring.
- ///
- /// The string to compare with the target substring.
- /// True if target substring starts with , false otherwise.
- public bool StartsWithStringByOrdinalComparison(string other) => String.CompareOrdinal(_target, _startIndex, other, 0, other.Length) == 0;
-
- ///
- /// Never reference equals to string.
- ///
- public bool ReferenceEquals(string other) => false;
- }
-
- #endregion
-}
diff --git a/src/Shared/OpportunisticIntern.cs b/src/Shared/OpportunisticIntern.cs
deleted file mode 100644
index 48d5d407c7c..00000000000
--- a/src/Shared/OpportunisticIntern.cs
+++ /dev/null
@@ -1,1093 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-#if !CLR2COMPATIBILITY
-using System.Collections.Concurrent;
-#endif
-using System.Text;
-using System.Linq;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using Microsoft.Build.Shared;
-using Microsoft.Build.Utilities;
-
-namespace Microsoft.Build
-{
- ///
- /// This class is used to selectively intern strings. It should be used at the point of new string creation.
- /// For example,
- ///
- /// string interned = OpportunisticIntern.Intern(String.Join(",",someStrings));
- ///
- /// There are currently two underlying implementations. The new default one in WeakStringCacheInterner is based on weak GC handles.
- /// The legacy one in BucketedPrioritizedStringList is available only as an escape hatch by setting an environment variable.
- ///
- /// The legacy implementation uses heuristics to decide whether it will be efficient to intern a string or not. There is no
- /// guarantee that a string will intern.
- ///
- /// The thresholds and sizes were determined by experimentation to give the best number of bytes saved
- /// at reasonable elapsed time cost.
- ///
- /// The new implementation interns all strings but maintains only weak references so it doesn't keep the strings alive.
- ///
- internal sealed class OpportunisticIntern
- {
- ///
- /// Defines the interner interface as we currently implement more than one.
- ///
- private interface IInternerImplementation
- {
- ///
- /// Converts the given internable candidate to its string representation. Efficient implementions have side-effects
- /// of caching the results to end up with as few duplicates on the managed heap as practical.
- ///
- string InterningToString(T candidate) where T : IInternable;
-
- ///
- /// Prints implementation specific interning statistics to the console.
- ///
- /// A string identifying the interner in the output.
- void ReportStatistics(string heading);
- }
-
- ///
- /// The singleton instance of OpportunisticIntern.
- ///
- private static OpportunisticIntern _instance = new OpportunisticIntern();
- internal static OpportunisticIntern Instance => _instance;
-
- private readonly bool _useLegacyInterner = Traits.Instance.UseLegacyStringInterner;
- private readonly bool _useSimpleConcurrency = Traits.Instance.UseSimpleInternConcurrency;
-
- ///
- /// The size of the small mru list.
- ///
- private readonly int _smallMruSize;
-
- ///
- /// The size of the large mru list.
- ///
- private readonly int _largeMruSize;
-
- ///
- /// The size of the huge mru list.
- ///
- private readonly int _hugeMruSize;
-
- ///
- /// The smallest size a string can be to be considered small.
- ///
- private readonly int _smallMruThreshold;
-
- ///
- /// The smallest size a string can be to be considered large.
- ///
- private readonly int _largeMruThreshold;
-
- ///
- /// The smallest size a string can be to be considered huge.
- ///
- private readonly int _hugeMruThreshold;
-
- ///
- /// The smallest size a string can be to be ginormous.
- /// 8K for large object heap.
- ///
- private readonly int _ginormousThreshold;
-
- ///
- /// The interner implementation in use.
- ///
- private IInternerImplementation _interner;
-
- #region Statistics
- ///
- /// What if Mru lists were infinitely long?
- ///
- private BucketedPrioritizedStringList _whatIfInfinite;
-
- ///
- /// What if we doubled the size of the Mru lists?
- ///
- private BucketedPrioritizedStringList _whatIfDoubled;
-
- ///
- /// What if we halved the size of the Mru lists?
- ///
- private BucketedPrioritizedStringList _whatIfHalved;
-
- ///
- /// What if the size of Mru lists was zero? (We still intern tiny strings in this case)
- ///
- private BucketedPrioritizedStringList _whatIfZero;
- #endregion
-
- private OpportunisticIntern()
- {
- _smallMruSize = AssignViaEnvironment("MSBUILDSMALLINTERNSIZE", 50);
- _largeMruSize = AssignViaEnvironment("MSBUILDLARGEINTERNSIZE", 100);
- _hugeMruSize = AssignViaEnvironment("MSBUILDHUGEINTERNSIZE", 100);
- _smallMruThreshold = AssignViaEnvironment("MSBUILDSMALLINTERNTHRESHOLD", 50);
- _largeMruThreshold = AssignViaEnvironment("MSBUILDLARGEINTERNTHRESHOLD", 70);
- _hugeMruThreshold = AssignViaEnvironment("MSBUILDHUGEINTERNTHRESHOLD", 200);
- _ginormousThreshold = AssignViaEnvironment("MSBUILDGINORMOUSINTERNTHRESHOLD", 8000);
-
- _interner = _useLegacyInterner
- ? (IInternerImplementation)new BucketedPrioritizedStringList(gatherStatistics: false, _smallMruSize, _largeMruSize, _hugeMruSize,
- _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency)
- : (IInternerImplementation)new WeakStringCacheInterner(gatherStatistics: false);
- }
-
- ///
- /// Recreates the singleton instance based on the current environment (test only).
- ///
- internal static void ResetForTests()
- {
- Debug.Assert(BuildEnvironmentHelper.Instance.RunningTests);
- _instance = new OpportunisticIntern();
- }
-
- ///
- /// Assign an int from an environment variable. If its not present, use the default.
- ///
- private int AssignViaEnvironment(string env, int @default)
- {
- string threshold = Environment.GetEnvironmentVariable(env);
- if (!string.IsNullOrEmpty(threshold))
- {
- if (int.TryParse(threshold, out int result))
- {
- return result;
- }
- }
-
- return @default;
- }
-
- ///
- /// Turn on statistics gathering.
- ///
- internal void EnableStatisticsGathering()
- {
- if (_useLegacyInterner)
- {
- // Statistics include several 'what if' scenarios such as doubling the size of the MRU lists.
- _interner = new BucketedPrioritizedStringList(gatherStatistics: true, _smallMruSize, _largeMruSize, _hugeMruSize, _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency);
- _whatIfInfinite = new BucketedPrioritizedStringList(gatherStatistics: true, int.MaxValue, int.MaxValue, int.MaxValue, _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency);
- _whatIfDoubled = new BucketedPrioritizedStringList(gatherStatistics: true, _smallMruSize * 2, _largeMruSize * 2, _hugeMruSize * 2, _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency);
- _whatIfHalved = new BucketedPrioritizedStringList(gatherStatistics: true, _smallMruSize / 2, _largeMruSize / 2, _hugeMruSize / 2, _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency);
- _whatIfZero = new BucketedPrioritizedStringList(gatherStatistics: true, 0, 0, 0, _smallMruThreshold, _largeMruThreshold, _hugeMruThreshold, _ginormousThreshold, _useSimpleConcurrency);
- }
- else
- {
- _interner = new WeakStringCacheInterner(gatherStatistics: true);
- }
- }
-
- ///
- /// Intern the given internable.
- ///
- internal static string InternableToString(T candidate) where T : IInternable
- {
- return Instance.InternableToStringImpl(candidate);
- }
-
- ///
- /// Potentially Intern the given string builder.
- ///
- internal static string StringBuilderToString(StringBuilder candidate)
- {
- return Instance.InternableToStringImpl(new StringBuilderInternTarget(candidate));
- }
-
- ///
- /// Potentially Intern the given char array.
- ///
- internal static string CharArrayToString(char[] candidate, int count)
- {
- return Instance.InternableToStringImpl(new CharArrayInternTarget(candidate, count));
- }
-
- ///
- /// Potentially Intern the given char array.
- ///
- internal static string CharArrayToString(char[] candidate, int startIndex, int count)
- {
- return Instance.InternableToStringImpl(new CharArrayInternTarget(candidate, startIndex, count));
- }
-
- ///
- /// Potentially Intern the given string.
- ///
- /// The string to intern.
- /// The interned string, or the same string if it could not be interned.
- internal static string InternStringIfPossible(string candidate)
- {
- return Instance.InternableToStringImpl(new StringInternTarget(candidate));
- }
-
- ///
- /// Intern the given internable.
- ///
- private string InternableToStringImpl(T candidate) where T : IInternable
- {
- if (candidate.Length == 0)
- {
- // As in the case that a property or itemlist has evaluated to empty.
- return string.Empty;
- }
-
- if (_whatIfInfinite != null)
- {
- _whatIfInfinite.InterningToString(candidate);
- _whatIfDoubled.InterningToString(candidate);
- _whatIfHalved.InterningToString(candidate);
- _whatIfZero.InterningToString(candidate);
- }
-
- string result = _interner.InterningToString(candidate);
-#if DEBUG
- string expected = candidate.ExpensiveConvertToString();
- if (!String.Equals(result, expected))
- {
- ErrorUtilities.ThrowInternalError("Interned string {0} should have been {1}", result, expected);
- }
-#endif
- return result;
- }
-
- ///
- /// Report statistics about interning. Don't call unless GatherStatistics has been called beforehand.
- ///
- internal void ReportStatistics()
- {
- _interner.ReportStatistics("Main");
- if (_useLegacyInterner)
- {
- _whatIfInfinite.ReportStatistics("if Infinite");
- _whatIfDoubled.ReportStatistics("if Doubled");
- _whatIfHalved.ReportStatistics("if Halved");
- _whatIfZero.ReportStatistics("if Zero");
- Console.WriteLine(" * Even for MRU size of zero there will still be some intern hits because of the tiny ");
- Console.WriteLine(" string matching (eg. 'true')");
- }
- }
-
- private static bool TryInternHardcodedString(T candidate, string str, ref string interned) where T : IInternable
- {
- Debug.Assert(candidate.Length == str.Length);
-
- if (candidate.StartsWithStringByOrdinalComparison(str))
- {
- interned = str;
- return true;
- }
- return false;
- }
-
- ///
- /// Try to match the candidate with small number of hardcoded interned string literals.
- /// The return value indicates how the string was interned (if at all).
- ///
- ///
- /// True if the candidate matched a hardcoded literal, null if it matched a "do not intern" string, false otherwise.
- ///
- private static bool? TryMatchHardcodedStrings(T candidate, out string interned) where T : IInternable
- {
- int length = candidate.Length;
- interned = null;
-
- // Each of the hard-coded small strings below showed up in a profile run with considerable duplication in memory.
- if (length == 2)
- {
- if (candidate[1] == '#')
- {
- if (candidate[0] == 'C')
- {
- interned = "C#";
- return true;
- }
-
- if (candidate[0] == 'F')
- {
- interned = "F#";
- return true;
- }
- }
-
- if (candidate[0] == 'V' && candidate[1] == 'B')
- {
- interned = "VB";
- return true;
- }
- }
- else if (length == 4)
- {
- if (TryInternHardcodedString(candidate, "TRUE", ref interned) ||
- TryInternHardcodedString(candidate, "True", ref interned) ||
- TryInternHardcodedString(candidate, "Copy", ref interned) ||
- TryInternHardcodedString(candidate, "true", ref interned) ||
- TryInternHardcodedString(candidate, "v4.0", ref interned))
- {
- return true;
- }
- }
- else if (length == 5)
- {
- if (TryInternHardcodedString(candidate, "FALSE", ref interned) ||
- TryInternHardcodedString(candidate, "false", ref interned) ||
- TryInternHardcodedString(candidate, "Debug", ref interned) ||
- TryInternHardcodedString(candidate, "Build", ref interned) ||
- TryInternHardcodedString(candidate, "Win32", ref interned))
- {
- return true;
- }
- }
- else if (length == 6)
- {
- if (TryInternHardcodedString(candidate, "''!=''", ref interned) ||
- TryInternHardcodedString(candidate, "AnyCPU", ref interned))
- {
- return true;
- }
- }
- else if (length == 7)
- {
- if (TryInternHardcodedString(candidate, "Library", ref interned) ||
- TryInternHardcodedString(candidate, "MSBuild", ref interned) ||
- TryInternHardcodedString(candidate, "Release", ref interned))
- {
- return true;
- }
- }
- // see Microsoft.Build.BackEnd.BuildRequestConfiguration.CreateUniqueGlobalProperty
- else if (length > MSBuildConstants.MSBuildDummyGlobalPropertyHeader.Length &&
- candidate.StartsWithStringByOrdinalComparison(MSBuildConstants.MSBuildDummyGlobalPropertyHeader))
- {
- // don't want to leak unique strings into the cache
- interned = candidate.ExpensiveConvertToString();
- return null;
- }
- else if (length == 24)
- {
- if (TryInternHardcodedString(candidate, "ResolveAssemblyReference", ref interned))
- {
- return true;
- }
- }
- return false;
- }
-
- ///
- /// Implements interning based on a WeakStringCache (new implementation).
- ///
- private class WeakStringCacheInterner : IInternerImplementation
- {
- ///
- /// Enumerates the possible interning results.
- ///
- private enum InternResult
- {
- MatchedHardcodedString,
- FoundInWeakStringCache,
- AddedToWeakStringCache,
- RejectedFromInterning
- }
-
- ///
- /// The cache to keep strings in.
- ///
- private readonly WeakStringCache _weakStringCache = new WeakStringCache();
-
-#region Statistics
- ///
- /// Whether or not to gather statistics.
- ///
- private readonly bool _gatherStatistics;
-
- ///
- /// Number of times interning with hardcoded string literals worked.
- ///
- private int _hardcodedInternHits;
-
- ///
- /// Number of times the regular interning path found the string in the cache.
- ///
- private int _regularInternHits;
-
- ///
- /// Number of times the regular interning path added the string to the cache.
- ///
- private int _regularInternMisses;
-
- ///
- /// Number of times interning wasn't attempted.
- ///
- private int _rejectedStrings;
-
- ///
- /// Total number of strings eliminated by interning.
- ///
- private int _internEliminatedStrings;
-
- ///
- /// Total number of chars eliminated across all strings.
- ///
- private int _internEliminatedChars;
-
- ///
- /// Maps strings that went though the regular (i.e. not hardcoded) interning path to the number of times they have been
- /// seen. The higher the number the better the payoff if the string had been hardcoded.
- ///
- private Dictionary _missedHardcodedStrings;
-
-#endregion
-
- public WeakStringCacheInterner(bool gatherStatistics)
- {
- if (gatherStatistics)
- {
- _missedHardcodedStrings = new Dictionary();
- }
- _gatherStatistics = gatherStatistics;
- }
-
- ///
- /// Intern the given internable.
- ///
- public string InterningToString(T candidate) where T : IInternable
- {
- if (_gatherStatistics)
- {
- return InternWithStatistics(candidate);
- }
- else
- {
- TryIntern(candidate, out string result);
- return result;
- }
- }
-
- ///
- /// Report statistics to the console.
- ///
- public void ReportStatistics(string heading)
- {
- string title = "Opportunistic Intern (" + heading + ")";
- Console.WriteLine("\n{0}{1}{0}", new string('=', 41 - (title.Length / 2)), title);
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Hardcoded Hits", _hardcodedInternHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Hardcoded Rejects", _rejectedStrings, "rejects");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Hits", _regularInternHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Misses", _regularInternMisses, "misses");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Strings*", _internEliminatedStrings, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Chars", _internEliminatedChars, "chars");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Estimated Eliminated Bytes", _internEliminatedChars * 2, "bytes");
- Console.WriteLine("Elimination assumes that strings provided were unique objects.");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
-
- IEnumerable topMissingHardcodedString =
- _missedHardcodedStrings
- .OrderByDescending(kv => kv.Value * kv.Key.Length)
- .Take(15)
- .Where(kv => kv.Value > 1)
- .Select(kv => string.Format(CultureInfo.InvariantCulture, "({1} instances x each {2} chars)\n{0}", kv.Key, kv.Value, kv.Key.Length));
-
- Console.WriteLine("##########Top Missing Hardcoded Strings: \n{0} ", string.Join("\n==============\n", topMissingHardcodedString.ToArray()));
- Console.WriteLine();
-
- WeakStringCache.DebugInfo debugInfo = _weakStringCache.GetDebugInfo();
- Console.WriteLine("WeakStringCache statistics:");
- Console.WriteLine("String count live/collected/total = {0}/{1}/{2}", debugInfo.LiveStringCount, debugInfo.CollectedStringCount, debugInfo.LiveStringCount + debugInfo.CollectedStringCount);
- }
-
- ///
- /// Try to intern the string.
- /// The return value indicates the how the string was interned (if at all).
- ///
- private InternResult TryIntern(T candidate, out string interned) where T : IInternable
- {
- // First, try the hard coded intern strings.
- bool? hardcodedMatchResult = TryMatchHardcodedStrings(candidate, out interned);
- if (hardcodedMatchResult != false)
- {
- // Either matched a hardcoded string or is explicitly not to be interned.
- return hardcodedMatchResult.HasValue ? InternResult.MatchedHardcodedString : InternResult.RejectedFromInterning;
- }
-
- interned = _weakStringCache.GetOrCreateEntry(candidate, out bool cacheHit);
- return cacheHit ? InternResult.FoundInWeakStringCache : InternResult.AddedToWeakStringCache;
- }
-
- ///
- /// Version of Intern that gathers statistics
- ///
- private string InternWithStatistics(T candidate) where T : IInternable
- {
- lock (_missedHardcodedStrings)
- {
- InternResult internResult = TryIntern(candidate, out string result);
-
- switch (internResult)
- {
- case InternResult.MatchedHardcodedString:
- _hardcodedInternHits++;
- break;
- case InternResult.FoundInWeakStringCache:
- _regularInternHits++;
- break;
- case InternResult.AddedToWeakStringCache:
- _regularInternMisses++;
- break;
- case InternResult.RejectedFromInterning:
- _rejectedStrings++;
- break;
- }
-
- if (internResult != InternResult.MatchedHardcodedString && internResult != InternResult.RejectedFromInterning)
- {
- _missedHardcodedStrings.TryGetValue(result, out int priorCount);
- _missedHardcodedStrings[result] = priorCount + 1;
- }
-
- if (!candidate.ReferenceEquals(result))
- {
- // Reference changed so 'candidate' is now released and should save memory.
- _internEliminatedStrings++;
- _internEliminatedChars += candidate.Length;
- }
-
- return result;
- }
- }
- }
-
- ///
- /// Manages a set of mru lists that hold strings in varying size ranges (legacy implementation).
- ///
- private class BucketedPrioritizedStringList : IInternerImplementation
- {
- ///
- /// The small string Mru list.
- ///
- private readonly PrioritizedStringList _smallMru;
-
- ///
- /// The large string Mru list.
- ///
- private readonly PrioritizedStringList _largeMru;
-
- ///
- /// The huge string Mru list.
- ///
- private readonly PrioritizedStringList _hugeMru;
-
- ///
- /// Three most recently used strings over 8K.
- ///
- private readonly LinkedList _ginormous = new LinkedList();
-
- ///
- /// The smallest size a string can be to be considered small.
- ///
- private readonly int _smallMruThreshold;
-
- ///
- /// The smallest size a string can be to be considered large.
- ///
- private readonly int _largeMruThreshold;
-
- ///
- /// The smallest size a string can be to be considered huge.
- ///
- private readonly int _hugeMruThreshold;
-
- ///
- /// The smallest size a string can be to be ginormous.
- ///
- private readonly int _ginormousThreshold;
-
- private readonly bool _useSimpleConcurrency;
-
-#if !CLR2COMPATIBILITY
- // ConcurrentDictionary starts with capacity 31 but we're usually adding far more than that. Make a better first capacity guess to reduce
- // ConcurrentDictionary having to take all internal locks to upgrade its bucket list. Note that the number should be prime per the
- // comments on the code at https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,122
- // Also note default lock count is NativeMethodsShared.GetLogicalCoreCount() from the same code.
- private const int InitialCapacity = 2053;
- private readonly ConcurrentDictionary _internedStrings = new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), InitialCapacity, StringComparer.Ordinal);
-#endif
-
-#region Statistics
- ///
- /// Whether or not to gather statistics
- ///
- private readonly bool _gatherStatistics;
-
- ///
- /// Number of times interning worked.
- ///
- private int _internHits;
-
- ///
- /// Number of times interning didn't work.
- ///
- private int _internMisses;
-
- ///
- /// Number of times interning wasn't attempted.
- ///
- private int _internRejects;
-
- ///
- /// Total number of strings eliminated by interning.
- ///
- private int _internEliminatedStrings;
-
- ///
- /// Total number of chars eliminated across all strings.
- ///
- private int _internEliminatedChars;
-
- ///
- /// Number of times the ginourmous string hit.
- ///
- private int _ginormousHits;
-
- ///
- /// Number of times the ginourmous string missed.
- ///
- private int _ginormousMisses;
-
- ///
- /// Chars interned for ginormous range.
- ///
- private int _ginormousCharsSaved;
-
- ///
- /// Whether or not to track ginormous strings.
- ///
- private readonly bool _dontTrack;
-
- ///
- /// The time spent interning.
- ///
- private readonly Stopwatch _stopwatch;
-
- ///
- /// Strings which did not intern
- ///
- private readonly Dictionary _missedStrings;
-
- ///
- /// Strings which we didn't attempt to intern
- ///
- private readonly Dictionary _rejectedStrings;
-
- ///
- /// Number of ginormous strings to keep
- /// By observation of Auto7, there are about three variations of the huge solution config blob
- /// There aren't really any other strings of this size, but make it 10 to be sure. (There will barely be any misses)
- ///
- private const int GinormousSize = 10;
-
-#endregion
-
- ///
- /// Construct.
- ///
- internal BucketedPrioritizedStringList(bool gatherStatistics, int smallMruSize, int largeMruSize, int hugeMruSize, int smallMruThreshold, int largeMruThreshold, int hugeMruThreshold, int ginormousThreshold, bool useSimpleConcurrency)
- {
- if (smallMruSize == 0 && largeMruSize == 0 && hugeMruSize == 0)
- {
- _dontTrack = true;
- }
-
- _smallMru = new PrioritizedStringList(smallMruSize);
- _largeMru = new PrioritizedStringList(largeMruSize);
- _hugeMru = new PrioritizedStringList(hugeMruSize);
- _smallMruThreshold = smallMruThreshold;
- _largeMruThreshold = largeMruThreshold;
- _hugeMruThreshold = hugeMruThreshold;
- _ginormousThreshold = ginormousThreshold;
- _useSimpleConcurrency = useSimpleConcurrency;
-
- for (int i = 0; i < GinormousSize; i++)
- {
- _ginormous.AddFirst(new WeakReference(string.Empty));
- }
-
- _gatherStatistics = gatherStatistics;
- if (gatherStatistics)
- {
- _stopwatch = new Stopwatch();
- _missedStrings = new Dictionary(StringComparer.Ordinal);
- _rejectedStrings = new Dictionary(StringComparer.Ordinal);
- }
- }
-
- ///
- /// Intern the given internable.
- ///
- public string InterningToString(T candidate) where T : IInternable
- {
- if (_gatherStatistics)
- {
- return InternWithStatistics(candidate);
- }
- else
- {
- TryIntern(candidate, out string result);
- return result;
- }
- }
-
- ///
- /// Report statistics to the console.
- ///
- public void ReportStatistics(string heading)
- {
- string title = "Opportunistic Intern (" + heading + ")";
- Console.WriteLine("\n{0}{1}{0}", new string('=', 41 - (title.Length / 2)), title);
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Hits", _internHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Misses", _internMisses, "misses");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Rejects (as shorter than " + _smallMruThreshold + " bytes)", _internRejects, "rejects");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Strings*", _internEliminatedStrings, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Chars", _internEliminatedChars, "chars");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Estimated Eliminated Bytes", _internEliminatedChars * 2, "bytes");
- Console.WriteLine("Elimination assumes that strings provided were unique objects.");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- KeyValuePair held = _smallMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Strings MRU Size", Instance._smallMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Strings (>=" + _smallMruThreshold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- held = _largeMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Strings MRU Size", Instance._largeMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Strings (>=" + _largeMruThreshold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- held = _hugeMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Strings MRU Size", Instance._hugeMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Strings (>=" + _hugeMruThreshold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Strings MRU Size", GinormousSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous (>=" + _ginormousThreshold + " chars) Hits", _ginormousHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Misses", _ginormousMisses, "misses");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Chars Saved", _ginormousCharsSaved, "chars");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
-
- // There's no point in reporting the ginormous string because it will have evaporated by now.
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Time Spent Interning", _stopwatch.ElapsedMilliseconds, "ms");
- Console.WriteLine("{0}{0}", new string('=', 41));
-
- IEnumerable topMissingString =
- _missedStrings
- .OrderByDescending(kv => kv.Value * kv.Key.Length)
- .Take(15)
- .Where(kv => kv.Value > 1)
- .Select(kv => string.Format(CultureInfo.InvariantCulture, "({1} instances x each {2} chars = {3}KB wasted)\n{0}", kv.Key, kv.Value, kv.Key.Length, (kv.Value - 1) * kv.Key.Length * 2 / 1024));
-
- Console.WriteLine("##########Top Missed Strings: \n{0} ", string.Join("\n==============\n", topMissingString.ToArray()));
- Console.WriteLine();
-
- IEnumerable topRejectedString =
- _rejectedStrings
- .OrderByDescending(kv => kv.Value * kv.Key.Length)
- .Take(15)
- .Where(kv => kv.Value > 1)
- .Select(kv => string.Format(CultureInfo.InvariantCulture, "({1} instances x each {2} chars = {3}KB wasted)\n{0}", kv.Key, kv.Value, kv.Key.Length, (kv.Value - 1) * kv.Key.Length * 2 / 1024));
-
- Console.WriteLine("##########Top Rejected Strings: \n{0} ", string.Join("\n==============\n", topRejectedString.ToArray()));
- }
-
- ///
- /// Try to intern the string.
- /// Return true if an interned value could be returned.
- /// Return false if it was added to the intern list, but wasn't there already.
- /// Return null if it didn't meet the length criteria for any of the buckets. Interning was rejected
- ///
- private bool? TryIntern(T candidate, out string interned) where T : IInternable
- {
- int length = candidate.Length;
- interned = null;
-
- // First, try the hard coded intern strings.
- // Each of the hard-coded small strings below showed up in a profile run with considerable duplication in memory.
- if (!_dontTrack)
- {
- bool? hardcodedMatchResult = TryMatchHardcodedStrings(candidate, out interned);
- if (hardcodedMatchResult != false)
- {
- // Either matched a hardcoded string or is explicitly not to be interned.
- return hardcodedMatchResult;
- }
-
- if (length > _ginormousThreshold)
- {
- lock (_ginormous)
- {
- LinkedListNode current = _ginormous.First;
-
- while (current != null)
- {
- if (current.Value.Target is string last && last.Length == candidate.Length && candidate.StartsWithStringByOrdinalComparison(last))
- {
- interned = last;
- _ginormousHits++;
- _ginormousCharsSaved += last.Length;
-
- _ginormous.Remove(current);
- _ginormous.AddFirst(current);
-
- return true;
- }
-
- current = current.Next;
- }
-
- _ginormousMisses++;
- interned = candidate.ExpensiveConvertToString();
-
- LinkedListNode lastNode = _ginormous.Last;
- _ginormous.RemoveLast();
- _ginormous.AddFirst(lastNode);
- lastNode.Value.Target = interned;
-
- return false;
- }
- }
-#if !CLR2COMPATIBILITY
- else if (_useSimpleConcurrency)
- {
- var stringified = candidate.ExpensiveConvertToString();
- interned = _internedStrings.GetOrAdd(stringified, stringified);
- return true;
- }
-#endif
- else if (length >= _hugeMruThreshold)
- {
- lock (_hugeMru)
- {
- return _hugeMru.TryGet(candidate, out interned);
- }
- }
- else if (length >= _largeMruThreshold)
- {
- lock (_largeMru)
- {
- return _largeMru.TryGet(candidate, out interned);
- }
- }
- else if (length >= _smallMruThreshold)
- {
- lock (_smallMru)
- {
- return _smallMru.TryGet(candidate, out interned);
- }
- }
- }
-
- interned = candidate.ExpensiveConvertToString();
- return null;
- }
-
- ///
- /// Version of Intern that gathers statistics
- ///
- private string InternWithStatistics(T candidate) where T : IInternable
- {
- lock (_missedStrings)
- {
- _stopwatch.Start();
- bool? interned = TryIntern(candidate, out string result);
- _stopwatch.Stop();
-
- if (interned.HasValue && !interned.Value)
- {
- // Could not intern.
- _internMisses++;
-
- _missedStrings.TryGetValue(result, out int priorCount);
- _missedStrings[result] = priorCount + 1;
-
- return result;
- }
- else if (interned == null)
- {
- // Decided not to attempt interning
- _internRejects++;
-
- _rejectedStrings.TryGetValue(result, out int priorCount);
- _rejectedStrings[result] = priorCount + 1;
-
- return result;
- }
-
- _internHits++;
- if (!candidate.ReferenceEquals(result))
- {
- // Reference changed so 'candidate' is now released and should save memory.
- _internEliminatedStrings++;
- _internEliminatedChars += candidate.Length;
- }
-
- return result;
- }
- }
-
- ///
- /// A singly linked list of strings where the most recently accessed string is at the top.
- /// Size expands up to a fixed number of strings.
- ///
- private class PrioritizedStringList
- {
- ///
- /// Maximum size of the mru list.
- ///
- private readonly int _size;
-
- ///
- /// Head of the mru list.
- ///
- private Node _mru;
-
- ///
- /// Construct an Mru list with a fixed maximum size.
- ///
- internal PrioritizedStringList(int size)
- {
- _size = size;
- }
-
- ///
- /// Try to get one element from the list. Upon leaving the function 'candidate' will be at the head of the Mru list.
- /// This function is not thread-safe.
- ///
- internal bool TryGet(T candidate, out string interned) where T : IInternable
- {
- if (_size == 0)
- {
- interned = candidate.ExpensiveConvertToString();
- return false;
- }
-
- int length = candidate.Length;
- Node secondPrior = null;
- Node prior = null;
- Node head = _mru;
- bool found = false;
- int itemCount = 0;
-
- while (head != null && !found)
- {
- if (head.Value.Length == length)
- {
- if (candidate.StartsWithStringByOrdinalComparison(head.Value))
- {
- found = true;
- }
- }
-
- if (!found)
- {
- secondPrior = prior;
- prior = head;
- head = head.Next;
- }
-
- itemCount++;
- }
-
- if (found)
- {
- // Move it to the top and return the interned version.
- if (prior != null)
- {
- if (!candidate.ReferenceEquals(head.Value))
- {
- // Wasn't at the top already, so move it there.
- prior.Next = head.Next;
- head.Next = _mru;
- _mru = head;
- interned = _mru.Value;
- return true;
- }
- else
- {
- // But don't move it up if there is reference equality so that multiple calls to Intern don't redundantly emphasize a string.
- interned = head.Value;
- return true;
- }
- }
- else
- {
- // Found the item in the top spot. No need to move anything.
- interned = _mru.Value;
- return true;
- }
- }
- else
- {
- // Not found. Create a new entry and place it at the top.
- Node old = _mru;
- _mru = new Node(candidate.ExpensiveConvertToString()) { Next = old };
-
- // Cache miss. Use this opportunity to discard any element over the max size.
- if (itemCount >= _size && secondPrior != null)
- {
- secondPrior.Next = null;
- }
-
- interned = _mru.Value;
- return false;
- }
- }
-
- ///
- /// Returns the number of strings held and the total number of chars held.
- ///
- internal KeyValuePair Statistics()
- {
- Node head = _mru;
- int chars = 0;
- int strings = 0;
- while (head != null)
- {
- chars += head.Value.Length;
- strings++;
- head = head.Next;
- }
-
- return new KeyValuePair(strings, chars);
- }
-
- ///
- /// Singly linked list node.
- ///
- private class Node
- {
- ///
- /// Construct a Node
- ///
- internal Node(string value)
- {
- Value = value;
- }
-
- ///
- /// The next node in the list.
- ///
- internal Node Next { get; set; }
-
- ///
- /// The held string.
- ///
- internal string Value { get; }
- }
- }
- }
- }
-}
diff --git a/src/Shared/Traits.cs b/src/Shared/Traits.cs
index 3522c0972c1..7aa78f0bbeb 100644
--- a/src/Shared/Traits.cs
+++ b/src/Shared/Traits.cs
@@ -44,16 +44,6 @@ public Traits()
///
public readonly bool CacheFileExistence = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileExistence"));
- ///
- /// Use the legacy string interning implementation based on MRU lists.
- ///
- public readonly bool UseLegacyStringInterner = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildUseLegacyStringInterner"));
-
- ///
- /// Eliminate locking in OpportunisticIntern at the expense of memory (in effect only if UseLegacyStringInterner is set).
- ///
- public readonly bool UseSimpleInternConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildUseSimpleInternConcurrency"));
-
public readonly bool UseSimpleProjectRootElementCacheConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildUseSimpleProjectRootElementCacheConcurrency"));
///
diff --git a/src/Shared/WeakStringCache.Concurrent.cs b/src/Shared/WeakStringCache.Concurrent.cs
deleted file mode 100644
index 318aeafc131..00000000000
--- a/src/Shared/WeakStringCache.Concurrent.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using Microsoft.Build.Shared;
-
-namespace Microsoft.Build
-{
- ///
- /// Implements the WeakStringCache functionality on modern .NET versions where ConcurrentDictionary is available.
- ///
- internal sealed partial class WeakStringCache : IDisposable
- {
- private readonly ConcurrentDictionary _stringsByHashCode;
-
- public WeakStringCache()
- {
- _stringsByHashCode = new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), _initialCapacity);
- }
-
- ///
- /// Main entrypoint of this cache. Tries to look up a string that matches the given internable. If it succeeds, returns
- /// the string and sets cacheHit to true. If the string is not found, calls ExpensiveConvertToString on the internable,
- /// adds the resulting string to the cache, and returns it, setting cacheHit to false.
- ///
- /// The internable describing the string we're looking for.
- /// true if match found in cache, false otherwise.
- /// A string matching the given internable.
- ///
- /// This method performs two operations on the underlying ConcurrentDictionary on both cache hit and cache miss.
- /// 1. It checks whether the dictionary has a matching entry. The entry is temporarily removed from the cache so it doesn't
- /// race with Scavenge() freeing GC handles. This is the first operation.
- /// 2a. If there is a matching entry, we extract the string out of it and put it back in the cache (the second operation).
- /// 2b. If there is an entry but it doesn't match, or there is no entry for the given hash code, we extract the string from
- /// the internable, set it on the entry, and add the entry (back) in the cache.
- ///
- public string GetOrCreateEntry(T internable, out bool cacheHit) where T : IInternable
- {
- int hashCode = GetInternableHashCode(internable);
-
- StringWeakHandle handle;
- string result;
- bool addingNewHandle = false;
-
- // Get the existing handle from the cache and assume ownership by removing it. We can't use the simple TryGetValue() here because
- // the Scavenge method running on another thread could free the handle from underneath us.
- if (_stringsByHashCode.TryRemove(hashCode, out handle))
- {
- result = handle.GetString(internable);
- if (result != null)
- {
- // We have a hit, put the handle back in the cache.
- if (!_stringsByHashCode.TryAdd(hashCode, handle))
- {
- // Another thread has managed to add a handle for the same hash code, so the one we got can be freed.
- handle.Free();
- }
- cacheHit = true;
- return result;
- }
- }
- else
- {
- handle = new StringWeakHandle();
- addingNewHandle = true;
- }
-
- // We don't have the string in the cache - create it.
- result = internable.ExpensiveConvertToString();
-
- // Set the handle to reference the new string and put it in the cache.
- handle.SetString(result);
- if (!_stringsByHashCode.TryAdd(hashCode, handle))
- {
- // Another thread has managed to add a handle for the same hash code, so the one we got can be freed.
- handle.Free();
- }
-
- // Remove unused handles if our heuristic indicates that it would be productive. Note that the _scavengeThreshold field
- // accesses are not protected from races. Being atomic (as guaranteed by the 32-bit data type) is enough here.
- if (addingNewHandle)
- {
- // Prevent the dictionary from growing forever with GC handles that don't reference live strings anymore.
- if (_stringsByHashCode.Count >= _scavengeThreshold)
- {
- // Before we start scavenging set _scavengeThreshold to a high value to effectively lock other threads from
- // running Scavenge at the same time (minus rare races).
- _scavengeThreshold = int.MaxValue;
- try
- {
- // Get rid of unused handles.
- Scavenge();
- }
- finally
- {
- // And do this again when the number of handles reaches double the current after-scavenge number.
- _scavengeThreshold = _stringsByHashCode.Count * 2;
- }
- }
- }
-
- cacheHit = false;
- return result;
- }
-
- ///
- /// Iterates over the cache and removes unused GC handles, i.e. handles that don't reference live strings.
- /// This is expensive so try to call such that the cost is amortized to O(1) per GetOrCreateEntry() invocation.
- ///
- public void Scavenge()
- {
- foreach (KeyValuePair entry in _stringsByHashCode)
- {
- if (!entry.Value.IsUsed && _stringsByHashCode.TryRemove(entry.Key, out StringWeakHandle removedHandle))
- {
- // Note that the removed handle may be different from the one we got from the enumerator so check again
- // and try to put it back if it's still in use.
- if (!removedHandle.IsUsed || !_stringsByHashCode.TryAdd(entry.Key, removedHandle))
- {
- removedHandle.Free();
- }
- }
- }
- }
-
- ///
- /// Returns internal debug counters calculated based on the current state of the cache.
- ///
- public DebugInfo GetDebugInfo()
- {
- return GetDebugInfoImpl();
- }
- }
-}
diff --git a/src/Shared/WeakStringCache.cs b/src/Shared/WeakStringCache.cs
deleted file mode 100644
index 22021e1a08a..00000000000
--- a/src/Shared/WeakStringCache.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Build
-{
- ///
- /// A cache of weak GC handles pointing to strings. Weak GC handles are functionally equivalent to WeakReference's but have less overhead
- /// (they're a struct as opposed to WR which is a finalizable class) at the expense of requiring manual lifetime management. As long as
- /// a string has an ordinary strong GC root elsewhere in the process and another string with the same hashcode hasn't reused the entry,
- /// the cache has a reference to it and can match it to an internable. When the string is collected, it is also automatically "removed"
- /// from the cache by becoming unrecoverable from the GC handle. GC handles that do not reference a live string anymore are freed lazily.
- ///
- internal sealed partial class WeakStringCache : IDisposable
- {
- ///
- /// Debug stats returned by GetDebugInfo().
- ///
- public struct DebugInfo
- {
- public int LiveStringCount;
- public int CollectedStringCount;
- }
-
- ///
- /// Holds a weak GC handle to a string. Shared by all strings with the same hash code and referencing the last such string we've seen.
- ///
- private struct StringWeakHandle
- {
- ///
- /// Weak GC handle to the last string of the given hashcode we've seen.
- ///
- public GCHandle WeakHandle;
-
- ///
- /// Returns true if the string referenced by the handle is still alive.
- ///
- public bool IsUsed => WeakHandle.Target != null;
-
- ///
- /// Returns the string referenced by this handle if it is equal to the given internable.
- ///
- /// The internable describing the string we're looking for.
- /// The string matching the internable or null if the handle is referencing a collected string or the string is different.
- public string GetString(T internable) where T : IInternable
- {
- if (WeakHandle.IsAllocated && WeakHandle.Target is string str)
- {
- if (internable.Length == str.Length &&
- internable.StartsWithStringByOrdinalComparison(str))
- {
- return str;
- }
- }
- return null;
- }
-
- ///
- /// Sets the handle to the given string. If the handle is still referencing another live string, that string is effectively forgotten.
- ///
- /// The string to set.
- public void SetString(string str)
- {
- if (!WeakHandle.IsAllocated)
- {
- // The handle is not allocated - allocate it.
- WeakHandle = GCHandle.Alloc(str, GCHandleType.Weak);
- }
- else
- {
- WeakHandle.Target = str;
- }
- }
-
- ///
- /// Frees the GC handle.
- ///
- public void Free()
- {
- WeakHandle.Free();
- }
- }
-
- ///
- /// Initial capacity of the underlying dictionary.
- ///
- private const int _initialCapacity = 503;
-
- ///
- /// The maximum size we let the collection grow before scavenging unused entries.
- ///
- private int _scavengeThreshold = _initialCapacity;
-
- ///
- /// Implements the simple yet very decently performing djb2 hash function (xor version).
- ///
- /// The internable to compute the hash code for.
- /// The 32-bit hash code.
- internal static int GetInternableHashCode(T internable) where T : IInternable
- {
- int hashCode = 5381;
- for (int i = 0; i < internable.Length; i++)
- {
- unchecked
- {
- hashCode = hashCode * 33 ^ internable[i];
- }
- }
- return hashCode;
- }
-
- ///
- /// Frees all GC handles and clears the cache.
- ///
- public void Dispose()
- {
- foreach (KeyValuePair entry in _stringsByHashCode)
- {
- entry.Value.Free();
- }
- _stringsByHashCode.Clear();
- }
-
- ///
- /// Returns internal debug counters calculated based on the current state of the cache.
- ///
- private DebugInfo GetDebugInfoImpl()
- {
- DebugInfo debugInfo = new DebugInfo();
-
- foreach (KeyValuePair entry in _stringsByHashCode)
- {
- if (entry.Value.IsUsed)
- {
- debugInfo.LiveStringCount++;
- }
- else
- {
- debugInfo.CollectedStringCount++;
- }
- }
-
- return debugInfo;
- }
- }
-}
diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj
index b5200e1248b..97daccd3aae 100644
--- a/src/Tasks/Microsoft.Build.Tasks.csproj
+++ b/src/Tasks/Microsoft.Build.Tasks.csproj
@@ -82,18 +82,6 @@
NGen.cs
-
- IInternable.cs
-
-
- WeakStringCache.cs
-
-
- WeakStringCache.Concurrent.cs
-
-
- OpportunisticIntern.cs
-
PropertyParser.cs
True
diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj
index 2b851f4526a..2fdd06afdd6 100644
--- a/src/Utilities/Microsoft.Build.Utilities.csproj
+++ b/src/Utilities/Microsoft.Build.Utilities.csproj
@@ -125,18 +125,6 @@
Shared\InprocTrackingNativeMethods.cs
-
- Shared\IInternable.cs
-
-
- Shared\WeakStringCache.cs
-
-
- Shared\WeakStringCache.Concurrent.cs
-
-
- Shared\OpportunisticIntern.cs
-
Shared\ReadOnlyEmptyCollection.cs