Esempio n. 1
0
 /// <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);
 }
Esempio n. 2
0
        /// <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);
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        /// <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;
 }
Esempio n. 6
0
        /// <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));
        }
Esempio n. 7
0
        /// <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));
        }
Esempio n. 8
0
 /// <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);
 }
Esempio n. 9
0
 public Enumerator(ref InternableString spanBuilder)
 {
     _string    = spanBuilder;
     _charIndex = -1;
 }