Home

Awesome

Ben.StringIntern

NuGet version (Ben.StringIntern) .NET 5.0 .NET Core 3.1

Inspired by this issue being closed: "API request: string.Intern(ReadOnlySpan<char> ...)" #28368

Shared pool is capped; with 2 generation LRU eviction and further evictions on Gen2 GC collections.

Example usuage

Collections

using Ben.Collections;

array = array.ToInternedArray();
list = list.ToInternedList();
dict = dict.ToInternedDictionary();
string val = stringBuilder.Intern();

var conDict = dict.ToInternedConcurrentDictionary();

Sql query

using static Ben.Collections.Specialized.StringCache;

while (await reader.ReadAsync())
{
    var id = reader.GetInt32(0);
    
    // Dedupe the string before you keep it
    var category = Intern(reader.GetString(1));
    
    // ...
}

API

namespace Ben.Collections.Specialized
{
    public class InternPool
    {
        // Thread-safe shared intern pool; bounded at some capacity, with some max length
        public static SharedInternPool Shared { get; }
        // Unbounded
        public InternPool();
        // Unbounded with prereserved capacity
        public InternPool(int capacity);
        // Capped size with prereserved capacity, entires evicted based on 2 generation LRU
        public InternPool(int capacity, int maxCount);
        // Capped size; max pooled string length, with prereserved capacity, entires evicted based on 2 generation LRU
        public InternPool(int capacity, int maxCount, int maxLength)
        
        // Deduplicated; unbounded
        public InternPool(IEnumerable<string> collection);
        // Deduplicated; capped size, entires evicted based on 2 generation LRU
        public InternPool(IEnumerable<string> collection, int maxCount);
    
        [return: NotNullIfNotNull("value")]
        public string? Intern(string? value);
        public string Intern(ReadOnlySpan<char> value);
        public string InternAscii(ReadOnlySpan<byte> asciiValue);
        public string InternUtf8(ReadOnlySpan<byte> utf8Value);
        
        // Gets the number of strings currently in the pool.
        public int Count { get; }
        // Count of strings checked
        public long Considered { get; }
        // Count of strings deduplicated
        public long Deduped { get; }
        // Count of strings added to the pool, may be larger than Count if there is a maxCount.
        public long Added { get; }
    }
}

Stats for Shared pool

Using the dotnet counters as InternPool

Get the proc id

> dotnet-counters ps

     14600 MyApp MyAppPath\MyApp.exe

Then query monitor that process

> dotnet-counters monitor InternPool --process-id 14600

Press p to pause, r to resume, q to quit.
    Status: Running

[InternPool]
    Considered (Count / 1 sec)                     4,497
    Deduped (Count / 1 sec)                        4,496
    Evicted (Count / 1 sec)                            0
    Total Considered                           1,357,121
    Total Count                                    7,811
    Total Deduped                              1,316,376
    Total Evicted                                 32,934
    Total Gen2 Sweeps                                  3

Building

dotnet build -c Release

Performance

cd tests/Benchmarks
dotnet run -c Release
MethodDatasetMeanErrorRatioAllocated
Default2M (20k-Values)284.7 ms5.30 ms1.0063991944 B
StringIntern_Instance2M (20k-Values)295.3 ms4.21 ms1.04639920 B
StringIntern_Shared2M (20k-Values)330.3 ms4.79 ms1.1612256 B
Default2M-Unique292.3 ms7.76 ms1.0079199856 B
StringIntern_Instance2M-Unique472.7 ms7.33 ms1.6279199856 B
StringIntern_Shared2M-Unique1,035.4 ms11.56 ms3.5479200144 B
DefaultTaxi-data327.3 ms4.23 ms1.0067108800 B
StringIntern_InstanceTaxi-data291.2 ms1.16 ms0.89224 B
StringIntern_SharedTaxi-data311.9 ms2.34 ms0.95456 B
Default2M-Unique-Long836.2 ms10.45 ms1.00460444288 B
StringIntern_Instance2M-Unique-Long1,242.7 ms8.06 ms1.49460444288 B
StringIntern_Shared2M-Unique-Long1,734.8 ms12.35 ms2.07460444784 B
Default2M (20k-Values-Long)736.5 ms33.14 ms1.00332438368 B
StringIntern_Instance2M (20k-Values-Long)368.1 ms2.59 ms0.503324384 B
StringIntern_Shared2M (20k-Values-Long)413.4 ms4.13 ms0.5632200 B

Contributing

See CONTRIBUTING.md for information on contributing to this project.

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the .NET Foundation Code of Conduct.

License

This project is licensed with the Apache License.