/// <summary> /// Get the reference value for the provided baseline value. /// </summary> /// <param name="baseline"></param> /// <returns></returns> public static string GetReference(string baseline) { if (s_DisableCache) { return(baseline); } if (baseline == null) { return(null); //can't do a reference to null anyway } if (baseline.Length == 0) { return(string.Empty);//this is a stock intered string. } string officialString = baseline; // We'll replace this with the official copy if there is one. try { object outerLock = s_Lock; // We need a copy we can null out when released. WeakStringCollection hashCodeCollisionNode = null; // Collects strings with the same hash code. try { System.Threading.Monitor.Enter(outerLock); // Usually gets released in our finally block. int baselineHashCode = baseline.GetHashCode(); if (s_StringReferences.TryGetValue(baselineHashCode, out hashCodeCollisionNode)) { // The lookup by hash code gets us close, now have the little collection check for it. if (hashCodeCollisionNode != null) { System.Threading.Monitor.Enter(hashCodeCollisionNode); // Lock the row so we can release the outer. System.Threading.Monitor.Exit(outerLock); // Release outer lock early to reduce contention. outerLock = null; // Mark the outer lock released so we don't release it again! // This call will actually replace our tempString with any official copy if one is found. hashCodeCollisionNode.PackAndOrAdd(ref officialString); // Row lock will be released in the finally block. } else { // This case shouldn't happen, but we can replace the null entry with a new little collection. s_StringReferences[baselineHashCode] = new WeakStringCollection(baseline); } } else { // Didn't find anything for that hash code, so make a new little collection to hold the given string. s_StringReferences.Add(baselineHashCode, new WeakStringCollection(baseline)); s_PeakReferenceSize = Math.Max(s_StringReferences.Count, s_PeakReferenceSize); } } finally { if (outerLock != null) { System.Threading.Monitor.Exit(outerLock); } if (hashCodeCollisionNode != null) { System.Threading.Monitor.Exit(hashCodeCollisionNode); } } } catch (Exception ex) { #if DEBUG Debug.WriteLine(string.Format("While trying to get the string reference for \"{0}\" an exception was thrown: {1}", baseline, ex.Message)); #endif GC.KeepAlive(ex); //just here to avoid a compiler warn in release mode } return(officialString); }
/// <summary> /// Check the cache for garbage collected values. /// </summary> public static void Pack() { if (s_DisableCache) { return; } //Our caller has a right to expect to never get an exception from this. try { #if DEBUG Stopwatch packTimer = Stopwatch.StartNew(); long elapsedMilliseconds; int deadNodesCount; int singletonNodesCount = 0; int multipleNodesCount = 0; int collapsedNodesCount = 0; // Nodes that had multiple but reduced to singleton. #endif int startNodesCount; // Declared here for use in DEBUG after the lock exits. KeyValuePair <int, WeakStringCollection>[] allNodes; List <int> deadNodes; lock (s_Lock) { startNodesCount = s_StringReferences.Count; if (startNodesCount == 0) { return; //nothing here, nothing to collect. } deadNodes = new List <int>((startNodesCount / 4) + 1); //assume we could wipe out 25% every time. // Make a snapshot of the little collection nodes in our table, so we can reduce contention while we pack. allNodes = new KeyValuePair <int, WeakStringCollection> [s_StringReferences.Count]; int currentIndex = 0; foreach (KeyValuePair <int, WeakStringCollection> node in s_StringReferences) { allNodes[currentIndex++] = node; } } foreach (KeyValuePair <int, WeakStringCollection> keyValuePair in allNodes) { WeakStringCollection currentReferencesList = keyValuePair.Value; if (currentReferencesList == null) { deadNodes.Add(keyValuePair.Key); // Hmmm, shouldn't be here. Let's remove the null node. continue; // Try the next node. } lock (currentReferencesList) { #if DEBUG bool singletonNode = (currentReferencesList.Count == 1); if (singletonNode) { singletonNodesCount++; } else { multipleNodesCount++; } #endif if (currentReferencesList.Pack() <= 0) { deadNodes.Add(keyValuePair.Key); } #if DEBUG else { if (singletonNode == false && currentReferencesList.Count == 1) { collapsedNodesCount++; } } #endif } } lock (s_Lock) // Get the outer lock again so we can remove the dead nodes. { //and now kill off our dead nodes. foreach (int deadNodeKey in deadNodes) { WeakStringCollection currentNode; if (s_StringReferences.TryGetValue(deadNodeKey, out currentNode)) { if (currentNode != null) { lock (currentNode) { if (currentNode.Count <= 0) // Check that it's still dead! { s_StringReferences.Remove(deadNodeKey); } } } else { // This case shouldn't happen, but if it does there's nothing to lock or check. Just remove it. s_StringReferences.Remove(deadNodeKey); } } } //Finally, if we have killed a good percentage off we really need to shrink the dictionary itself. if (s_PeakReferenceSize > MinimumRebuildSize) { double fillRatio = (s_StringReferences.Count / (double)((s_PeakReferenceSize == 0) ? 1 : s_PeakReferenceSize)); if (fillRatio < FreeSpaceRebuildRatio) { //it's bad enough we want to free the dictionary & rebuild it to make it small again. #if DEBUG Debug.WriteLine(string.Format("StringReference: Rebuilding collection because fill ratio is {0}", fillRatio)); #endif Dictionary <int, WeakStringCollection> newCollection = new Dictionary <int, WeakStringCollection>(s_StringReferences); s_StringReferences = newCollection; s_PeakReferenceSize = newCollection.Count; } } #if DEBUG packTimer.Stop(); elapsedMilliseconds = packTimer.ElapsedMilliseconds; deadNodesCount = deadNodes.Count; #endif System.Threading.Monitor.PulseAll(s_Lock); } #if DEBUG Debug.WriteLine(string.Format("StringReference: Pack took {0} ms.", elapsedMilliseconds)); Debug.WriteLine(string.Format("StringReference: Removed {0} of {1} nodes from the cache ({2:F2} %).", deadNodesCount, startNodesCount, (deadNodesCount * 100.0) / startNodesCount)); Debug.WriteLine(string.Format("StringReference: {0} singleton nodes vs {1} multiple nodes ({2} collapsed to singleton)", singletonNodesCount, multipleNodesCount, collapsedNodesCount)); #endif } catch (Exception ex) { GC.KeepAlive(ex); //just here to avoid a compiler warn in release mode } }