/// <summary> /// Returns the string referenced by this handle if it is equal to the given internable. /// </summary> /// <param name="internable">The internable describing the string we're looking for.</param> /// <returns>The string matching the internable or null if the handle is referencing a collected string or the string is different.</returns> public string?GetString(ref InternableString internable) { if (WeakHandle.IsAllocated && WeakHandle.Target is string str) { if (internable.Equals(str)) { return(str); } } return(null); }
/// <summary> /// 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. /// </summary> /// <param name="internable">The internable describing the string we're looking for.</param> /// <returns>A string matching the given internable.</returns> public string GetOrCreateEntry(ref InternableString internable, out bool cacheHit) { int hashCode = internable.GetHashCode(); StringWeakHandle handle; string? result; bool addingNewHandle = false; lock (_stringsByHashCode) { if (_stringsByHashCode.TryGetValue(hashCode, out handle)) { result = handle.GetString(ref 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); }
/// <summary> /// WeakIntern the given InternableString. /// </summary> public string InternableToString(ref InternableString candidate) { if (candidate.Length == 0) { return(string.Empty); } InternResult resultForStatistics = Intern(ref candidate, out string internedString); #if DEBUG string expectedString = candidate.ExpensiveConvertToString(); if (!String.Equals(internedString, expectedString)) { throw new InvalidOperationException(String.Format("Interned string {0} should have been {1}", internedString, expectedString)); } #endif if (_internCallCountsByString != null) { lock (_internCallCountsByString) { switch (resultForStatistics) { case InternResult.FoundInWeakStringCache: _regularInternHits++; break; case InternResult.AddedToWeakStringCache: _regularInternMisses++; break; } _internCallCountsByString.TryGetValue(internedString, out int priorCount); _internCallCountsByString[internedString] = priorCount + 1; if (!candidate.ReferenceEquals(internedString)) { // Reference changed so 'candidate' is now released and should save memory. _internEliminatedStrings++; _internEliminatedChars += candidate.Length; } } } return(internedString); }
/// <summary> /// 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. /// </summary> /// <param name="internable">The internable describing the string we're looking for.</param> /// <param name="cacheHit">true if match found in cache, false otherwise.</param> /// <returns>A string matching the given internable.</returns> public string GetOrCreateEntry(ref InternableString internable, out bool cacheHit) { int hashCode = internable.GetHashCode(); StringWeakHandle?handle; string? result; // Get the existing handle from the cache and lock it while we're dereferencing it to prevent a race with the Scavenge // method running on another thread and freeing the handle from underneath us. if (_stringsByHashCode.TryGetValue(hashCode, out handle)) { lock (handle) { result = handle.GetString(ref internable); if (result != null) { cacheHit = true; return(result); } // We have the handle but it's not referencing the right string - create the right string and store it in the handle. result = internable.ExpensiveConvertToString(); handle.SetString(result); cacheHit = false; return(result); } } // We don't have the handle in the cache - create the right string, store it in the handle, and add the handle to the cache. result = internable.ExpensiveConvertToString(); handle = new StringWeakHandle(); handle.SetString(result); _stringsByHashCode.TryAdd(hashCode, handle); // Remove unused handles if our heuristic indicates that it would be productive. int scavengeThreshold = _scavengeThreshold; 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. if (Interlocked.CompareExchange(ref _scavengeThreshold, int.MaxValue, scavengeThreshold) == scavengeThreshold) { 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); }
internal Enumerator(ref InternableString str) { _string = str; _spanIndex = -1; _charIndex = -1; }
/// <summary> /// Interns the given readonly span of characters, keeping only a weak reference to the returned value. /// </summary> /// <param name="str">The character span to intern.</param> /// <returns>A string equal to <paramref name="str"/>, could be the result of calling ToString() on <paramref name="str"/>.</returns> /// <remarks> /// The intern pool does not retain strong references to the strings it's holding so strings are automatically evicted /// after they become unrooted. This is in contrast to <c>System.String.Intern</c> which holds strings forever. /// </remarks> public static string WeakIntern(ReadOnlySpan <char> str) { InternableString internableString = new InternableString(str); return(WeakStringCacheInterner.Instance.InternableToString(ref internableString)); }
/// <summary> /// Interns the given string, keeping only a weak reference to the returned value. /// </summary> /// <param name="str">The string to intern.</param> /// <returns>A string equal to <paramref name="str"/>, could be the same object as <paramref name="str"/>.</returns> /// <remarks> /// The intern pool does not retain strong references to the strings it's holding so strings are automatically evicted /// after they become unrooted. This is in contrast to <c>System.String.Intern</c> which holds strings forever. /// </remarks> public static string WeakIntern(string str) { InternableString internableString = new InternableString(str); return(WeakStringCacheInterner.Instance.InternableToString(ref internableString)); }
/// <summary> /// Try to intern the string. /// The return value indicates the how the string was interned. /// </summary> private InternResult Intern(ref InternableString candidate, out string interned) { interned = _weakStringCache.GetOrCreateEntry(ref candidate, out bool cacheHit); return(cacheHit ? InternResult.FoundInWeakStringCache : InternResult.AddedToWeakStringCache); }
public Enumerator(ref InternableString spanBuilder) { _string = spanBuilder; _charIndex = -1; }