diff --git a/BitFaster.Caching/BitOps.cs b/BitFaster.Caching/BitOps.cs index 0ea28129..0edfb512 100644 --- a/BitFaster.Caching/BitOps.cs +++ b/BitFaster.Caching/BitOps.cs @@ -2,13 +2,26 @@ namespace BitFaster.Caching { + /// + /// Provides utility methods for bit-twiddling operations. + /// public class BitOps { + /// + /// Calculate the smallest power of 2 greater than the input parameter. + /// + /// The input parameter. + /// Smallest power of two greater than or equal to x. public static int CeilingPowerOfTwo(int x) { return (int)CeilingPowerOfTwo((uint)x); } + /// + /// Calculate the smallest power of 2 greater than the input parameter. + /// + /// The input parameter. + /// Smallest power of two greater than or equal to x. public static uint CeilingPowerOfTwo(uint x) { #if NETSTANDARD2_0 @@ -26,11 +39,21 @@ public static uint CeilingPowerOfTwo(uint x) } + /// + /// Counts the number of 1 bits in the input parameter. + /// + /// The input parameter. + /// The number of 1 bits. public static int BitCount(int x) { return BitCount((uint)x); } + /// + /// Counts the number of 1 bits in the input parameter. + /// + /// The input parameter. + /// The number of 1 bits. public static int BitCount(uint x) { #if NETSTANDARD2_0 @@ -47,11 +70,21 @@ public static int BitCount(uint x) #endif } + /// + /// Counts the number of 1 bits in the input parameter. + /// + /// The input parameter. + /// The number of 1 bits. public static int BitCount(long x) { return BitCount((ulong)x); } + /// + /// Counts the number of 1 bits in the input parameter. + /// + /// The input parameter. + /// The number of 1 bits. public static int BitCount(ulong x) { #if NETSTANDARD2_0 @@ -68,8 +101,14 @@ public static int BitCount(ulong x) #endif } - // Computes Stafford variant 13 of 64-bit mix function. - // http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html + /// + /// Computes Stafford variant 13 of 64-bit mix function. + /// + /// The input parameter. + /// A bit mix of the input parameter. + /// + /// See http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html + /// public static ulong Mix64(ulong z) { z = (z ^ z >> 30) * 0xbf58476d1ce4e5b9L; diff --git a/BitFaster.Caching/Buffers/MpmcBoundedBuffer.cs b/BitFaster.Caching/Buffers/MpmcBoundedBuffer.cs index 5cf75042..40b84555 100644 --- a/BitFaster.Caching/Buffers/MpmcBoundedBuffer.cs +++ b/BitFaster.Caching/Buffers/MpmcBoundedBuffer.cs @@ -19,6 +19,11 @@ public sealed class MpmcBoundedBuffer private readonly int slotsMask; private PaddedHeadAndTail headAndTail; // mutable struct, don't mark readonly + /// + /// Initializes a new instance of the MpmcBoundedBuffer class with the specified bounded capacity. + /// + /// The bounded length. + /// public MpmcBoundedBuffer(int boundedLength) { if (boundedLength < 0) @@ -54,6 +59,9 @@ public MpmcBoundedBuffer(int boundedLength) } } + /// + /// Gets the number of items contained in the buffer. + /// public int Count { get @@ -87,8 +95,16 @@ private int GetCount(int head, int tail) return 0; } + /// + /// The bounded capacity. + /// public int Capacity => slots.Length; + /// + /// Tries to remove an item. + /// + /// The item to be removed. + /// A BufferStatus value indicating whether the operation succeeded. public BufferStatus TryTake(out T item) { // Get the head at which to try to dequeue. @@ -145,6 +161,11 @@ public BufferStatus TryTake(out T item) return BufferStatus.Contended; } + /// + /// Tries to add the specified item. + /// + /// The item to be added. + /// A BufferStatus value indicating whether the operation succeeded. public BufferStatus TryAdd(T item) { // Get the tail at which to try to return. @@ -189,7 +210,12 @@ public BufferStatus TryAdd(T item) return BufferStatus.Contended; } - // Not thread safe + /// + /// Removes all values from the buffer. + /// + /// + /// Not thread safe. + /// public void Clear() { slots = new Slot[slots.Length]; diff --git a/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs b/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs index fcd0c727..2331e048 100644 --- a/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs +++ b/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs @@ -19,6 +19,11 @@ public sealed class MpscBoundedBuffer where T : class private readonly int mask; private PaddedHeadAndTail headAndTail; // mutable struct, don't mark readonly + /// + /// Initializes a new instance of the MpscBoundedBuffer class with the specified bounded capacity. + /// + /// The bounded length. + /// public MpscBoundedBuffer(int boundedLength) { if (boundedLength < 0) @@ -33,8 +38,14 @@ public MpscBoundedBuffer(int boundedLength) mask = boundedLength - 1; } + /// + /// The bounded capacity. + /// public int Capacity => buffer.Length; + /// + /// Gets the number of items contained in the buffer. + /// public int Count { get @@ -68,7 +79,14 @@ private int GetCount(int head, int tail) return 0; } - // thread safe + /// + /// Tries to add the specified item. + /// + /// The item to be added. + /// A BufferStatus value indicating whether the operation succeeded. + /// + /// Thread safe. + /// public BufferStatus TryAdd(T item) { int head = Volatile.Read(ref headAndTail.Head); @@ -91,7 +109,15 @@ public BufferStatus TryAdd(T item) return BufferStatus.Contended; } - // thread safe for single try take/drain + multiple try add + + /// + /// Tries to remove an item. + /// + /// The item to be removed. + /// A BufferStatus value indicating whether the operation succeeded. + /// + /// Thread safe for single try take/drain + multiple try add. + /// public BufferStatus TryTake(out T item) { int head = Volatile.Read(ref headAndTail.Head); @@ -119,7 +145,14 @@ public BufferStatus TryTake(out T item) return BufferStatus.Success; } - // thread safe for single try take/drain + multiple try add + /// + /// Drains the buffer into the specified array segment. + /// + /// The output buffer + /// The number of items written to the output buffer. + /// + /// Thread safe for single try take/drain + multiple try add. + /// public int DrainTo(ArraySegment output) { int head = Volatile.Read(ref headAndTail.Head); @@ -156,7 +189,12 @@ public int DrainTo(ArraySegment output) return outCount; } - // Not thread safe + /// + /// Removes all values from the buffer. + /// + /// + /// Not thread safe. + /// public void Clear() { buffer = new T[buffer.Length]; diff --git a/BitFaster.Caching/Buffers/StripedMpmcBuffer.cs b/BitFaster.Caching/Buffers/StripedMpmcBuffer.cs index b291231e..34470788 100644 --- a/BitFaster.Caching/Buffers/StripedMpmcBuffer.cs +++ b/BitFaster.Caching/Buffers/StripedMpmcBuffer.cs @@ -14,11 +14,20 @@ public sealed class StripedMpmcBuffer private MpmcBoundedBuffer[] buffers; + /// + /// Initializes a new instance of the StripedMpmcBuffer class with the specified stripe count and buffer size. + /// + /// The stripe count. + /// The buffer size. public StripedMpmcBuffer(int stripeCount, int bufferSize) : this(new StripedBufferSize(bufferSize, stripeCount)) { } + /// + /// Initializes a new instance of the StripedMpmcBuffer class with the specified buffer size. + /// + /// The buffer size. public StripedMpmcBuffer(StripedBufferSize bufferSize) { buffers = new MpmcBoundedBuffer[bufferSize.StripeCount]; @@ -29,8 +38,19 @@ public StripedMpmcBuffer(StripedBufferSize bufferSize) } } + /// + /// The bounded capacity. + /// public int Capacity => buffers.Length * buffers[0].Capacity; + /// + /// Drains the buffer into the specified output buffer. + /// + /// The output buffer + /// The number of items written to the output buffer. + /// + /// Thread safe. + /// public int DrainTo(T[] outputBuffer) { var count = 0; @@ -53,6 +73,14 @@ public int DrainTo(T[] outputBuffer) return count; } + /// + /// Tries to add the specified item. + /// + /// The item to be added. + /// A BufferStatus value indicating whether the operation succeeded. + /// + /// Thread safe. + /// public BufferStatus TryAdd(T item) { var z = BitOps.Mix64((ulong)Environment.CurrentManagedThreadId); @@ -78,6 +106,12 @@ public BufferStatus TryAdd(T item) return result; } + /// + /// Removes all values from the buffer. + /// + /// + /// Not thread safe. + /// public void Clear() { for (var i = 0; i < buffers.Length; i++) diff --git a/BitFaster.Caching/Buffers/StripedMpscBuffer.cs b/BitFaster.Caching/Buffers/StripedMpscBuffer.cs index 437ba233..7b5c6fed 100644 --- a/BitFaster.Caching/Buffers/StripedMpscBuffer.cs +++ b/BitFaster.Caching/Buffers/StripedMpscBuffer.cs @@ -15,13 +15,22 @@ public sealed class StripedMpscBuffer where T : class { const int MaxAttempts = 3; - private MpscBoundedBuffer[] buffers; + private readonly MpscBoundedBuffer[] buffers; + /// + /// Initializes a new instance of the StripedMpscBuffer class with the specified stripe count and buffer size. + /// + /// The stripe count. + /// The buffer size. public StripedMpscBuffer(int stripeCount, int bufferSize) : this(new StripedBufferSize(bufferSize, stripeCount)) { } + /// + /// Initializes a new instance of the StripedMpscBuffer class with the specified buffer size. + /// + /// The buffer size. public StripedMpscBuffer(StripedBufferSize bufferSize) { buffers = new MpscBoundedBuffer[bufferSize.StripeCount]; @@ -32,10 +41,24 @@ public StripedMpscBuffer(StripedBufferSize bufferSize) } } + /// + /// Gets the number of items contained in the buffer. + /// public int Count => buffers.Sum(b => b.Count); + /// + /// The bounded capacity. + /// public int Capacity => buffers.Length * buffers[0].Capacity; + /// + /// Drains the buffer into the specified array segment. + /// + /// The output buffer + /// The number of items written to the output buffer. + /// + /// Thread safe for single try take/drain + multiple try add. + /// public int DrainTo(T[] outputBuffer) { var count = 0; @@ -54,6 +77,14 @@ public int DrainTo(T[] outputBuffer) return count; } + /// + /// Tries to add the specified item. + /// + /// The item to be added. + /// A BufferStatus value indicating whether the operation succeeded. + /// + /// Thread safe. + /// public BufferStatus TryAdd(T item) { var z = BitOps.Mix64((ulong)Environment.CurrentManagedThreadId); @@ -79,6 +110,12 @@ public BufferStatus TryAdd(T item) return result; } + /// + /// Removes all values from the buffer. + /// + /// + /// Not thread safe. + /// public void Clear() { for (var i = 0; i < buffers.Length; i++) diff --git a/BitFaster.Caching/Concurrent/LongAdder.cs b/BitFaster.Caching/Concurrent/LongAdder.cs index 986ed7ee..da45ef3e 100644 --- a/BitFaster.Caching/Concurrent/LongAdder.cs +++ b/BitFaster.Caching/Concurrent/LongAdder.cs @@ -9,10 +9,20 @@ namespace BitFaster.Caching.Concurrent { + /// + /// A thread-safe counter suitable for high throuhgput counting across many concurrent threads. + /// public sealed class LongAdder : Striped64 { + /// + /// Creates a new LongAdder with an intial sum of zero. + /// public LongAdder() { } + /// + /// Computes the current sum. + /// + /// The current sum. public long Sum() { var @as = this.Cells; Cell a; @@ -28,11 +38,18 @@ public long Sum() return sum; } + /// + /// Increment by 1. + /// public void Increment() { Add(1L); } + /// + /// Adds the specified value. + /// + /// The value to add. public void Add(long value) { Cell[] @as; diff --git a/BitFaster.Caching/Concurrent/Striped64.cs b/BitFaster.Caching/Concurrent/Striped64.cs index e3d757b9..a0403a85 100644 --- a/BitFaster.Caching/Concurrent/Striped64.cs +++ b/BitFaster.Caching/Concurrent/Striped64.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.ConstrainedExecution; using System.Threading; /* @@ -75,20 +76,43 @@ namespace BitFaster.Caching.Concurrent * contention levels will recur, so the cells will eventually be * needed again; and for short-lived ones, it does not matter. */ + + /// + /// Mmaintains a lazily-initialized table of atomically updated variables, plus an extra + /// "base" field. The table size is a power of two. Indexing uses masked thread IDs. + /// [ExcludeFromCodeCoverage] public abstract class Striped64 { // Number of CPUS, to place bound on table size private static readonly int MaxBuckets = Environment.ProcessorCount * 4; + /// + /// The base value used mainly when there is no contention, but also as a fallback + /// during table initialization races. Updated via CAS. + /// protected PaddedLong @base = new PaddedLong(); + + /// + /// When non-null, size is a power of 2. + /// protected Cell[] Cells; private int cellsBusy; + /// + /// A wrapper for PaddedLong. + /// protected sealed class Cell { + /// + /// The value of the cell. + /// public PaddedLong value; + /// + /// Initializes a new cell with the specified value. + /// + /// The value. public Cell(long x) { this.value = new PaddedLong() { value = x }; diff --git a/BitFaster.Caching/Lfu/CmSketch.cs b/BitFaster.Caching/Lfu/CmSketch.cs index bd42fdf1..b5c9101d 100644 --- a/BitFaster.Caching/Lfu/CmSketch.cs +++ b/BitFaster.Caching/Lfu/CmSketch.cs @@ -15,9 +15,9 @@ namespace BitFaster.Caching.Lfu public sealed class CmSketch { // A mixture of seeds from FNV-1a, CityHash, and Murmur3 - private static ulong[] Seed = { 0xc3a5c85c97cb3127L, 0xb492b66fbe98f273L, 0x9ae16a3b2f90404fL, 0xcbf29ce484222325L}; - private static long ResetMask = 0x7777777777777777L; - private static long OneMask = 0x1111111111111111L; + private static readonly ulong[] Seed = { 0xc3a5c85c97cb3127L, 0xb492b66fbe98f273L, 0x9ae16a3b2f90404fL, 0xcbf29ce484222325L}; + private static readonly long ResetMask = 0x7777777777777777L; + private static readonly long OneMask = 0x1111111111111111L; private int sampleSize; private int tableMask; @@ -26,16 +26,32 @@ public sealed class CmSketch private readonly IEqualityComparer comparer; + /// + /// Initializes a new instance of the CmSketch class with the specified maximum size and equality comparer. + /// + /// The maximum size. + /// The equality comparer. public CmSketch(long maximumSize, IEqualityComparer comparer) { EnsureCapacity(maximumSize); this.comparer = comparer; } + /// + /// Gets the reset sample size. + /// public int ResetSampleSize => this.sampleSize; + /// + /// Gets the size. + /// public int Size => this.size; + /// + /// Initialize such that the count min sketch can accurately estimate the count for values given + /// a maximum size. + /// + /// The maximum size. public void EnsureCapacity(long maximumSize) { int maximum = (int)Math.Min(maximumSize, int.MaxValue >> 1); @@ -47,6 +63,11 @@ public void EnsureCapacity(long maximumSize) size = 0; } + /// + /// Estimate the frequency of the specified value. + /// + /// The value. + /// The estimated frequency of the value. public int EstimateFrequency(T value) { int hash = Spread(comparer.GetHashCode(value)); @@ -63,6 +84,10 @@ public int EstimateFrequency(T value) return frequency; } + /// + /// Increment the count of the specified value. + /// + /// The value. public void Increment(T value) { int hash = Spread(comparer.GetHashCode(value)); @@ -108,6 +133,9 @@ private void Reset() size = (size - (count >> 2)) >> 1; } + /// + /// Clears the count for all items. + /// public void Clear() { table = new long[table.Length]; diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index 81f7ec8f..187bcac6 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -60,11 +60,23 @@ public sealed class ConcurrentLfu : ICache, IAsyncCache, IBoun private readonly LfuNode[] drainBuffer; #endif + /// + /// Initializes a new instance of the ConcurrentLfu class with the specified capacity. + /// + /// The capacity. public ConcurrentLfu(int capacity) : this(Defaults.ConcurrencyLevel, capacity, new ThreadPoolScheduler(), EqualityComparer.Default, LfuBufferSize.Default(Defaults.ConcurrencyLevel, capacity)) { } + /// + /// Initializes a new instance of the ConcurrentLfu class with the specified concurrencyLevel, capacity, scheduler, equality comparer and buffer size. + /// + /// The concurrency level. + /// The capacity. + /// The scheduler. + /// The equality comparer. + /// The buffer size. public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler, IEqualityComparer comparer, LfuBufferSize bufferSize) { this.dictionary = new ConcurrentDictionary>(concurrencyLevel, capacity, comparer); @@ -87,20 +99,30 @@ public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler, I #endif } + /// public int Count => this.dictionary.Count; + /// public int Capacity => this.capacity.Capacity; + /// public Optional Metrics => new Optional(this.metrics); + /// public Optional> Events => Optional>.None(); + /// public CachePolicy Policy => new CachePolicy(new Optional(this), Optional.None()); + /// public ICollection Keys => this.dictionary.Keys; + /// + /// Gets the scheduler. + /// public IScheduler Scheduler => scheduler; + /// public void AddOrUpdate(K key, V value) { while (true) @@ -119,6 +141,7 @@ public void AddOrUpdate(K key, V value) } } + /// public void Clear() { this.Trim(this.Count); @@ -131,6 +154,10 @@ public void Clear() } } + /// + /// Trim the specified number of items from the cache. + /// + /// The number of items to remove. public void Trim(int itemCount) { itemCount = Math.Min(itemCount, this.Count); @@ -154,6 +181,7 @@ public void Trim(int itemCount) } } + /// public V GetOrAdd(K key, Func valueFactory) { while (true) @@ -172,6 +200,7 @@ public V GetOrAdd(K key, Func valueFactory) } } + /// public async ValueTask GetOrAddAsync(K key, Func> valueFactory) { while (true) @@ -190,6 +219,7 @@ public async ValueTask GetOrAddAsync(K key, Func> valueFactory) } } + /// public bool TryGet(K key, out V value) { if (this.dictionary.TryGetValue(key, out var node)) @@ -210,6 +240,7 @@ public bool TryGet(K key, out V value) return false; } + /// public bool TryRemove(K key) { if (this.dictionary.TryRemove(key, out var node)) @@ -222,6 +253,7 @@ public bool TryRemove(K key) return false; } + /// public bool TryUpdate(K key, V value) { if (this.dictionary.TryGetValue(key, out var node)) @@ -238,11 +270,23 @@ public bool TryUpdate(K key, V value) return false; } + /// + /// Synchronously perform all pending maintenance. Draining the read and write buffers then + /// use the eviction policy to preserve bounded size and remove expired items. + /// public void PendingMaintenance() { DrainBuffers(); } + /// Returns an enumerator that iterates through the cache. + /// An enumerator for the cache. + /// + /// The enumerator returned from the cache is safe to use concurrently with + /// reads and writes, however it does not represent a moment-in-time snapshot. + /// The contents exposed through the enumerator may contain modifications + /// made after was called. + /// public IEnumerator> GetEnumerator() { foreach (var kvp in this.dictionary) @@ -744,7 +788,7 @@ public string FormatLruString() [ExcludeFromCodeCoverage] internal class LfuDebugView { - private ConcurrentLfu lfu; + private readonly ConcurrentLfu lfu; public LfuDebugView(ConcurrentLfu lfu) { diff --git a/BitFaster.Caching/Lru/ConcurrentLruCore.cs b/BitFaster.Caching/Lru/ConcurrentLruCore.cs index 343609e2..6b6c4059 100644 --- a/BitFaster.Caching/Lru/ConcurrentLruCore.cs +++ b/BitFaster.Caching/Lru/ConcurrentLruCore.cs @@ -49,8 +49,17 @@ public class ConcurrentLruCore : ICache, IAsyncCache, // Since T is a struct, making it readonly will force the runtime to make defensive copies // if mutate methods are called. Therefore, field must be mutable to maintain count. - protected T telemetryPolicy; + private T telemetryPolicy; + /// + /// Initializes a new instance of the ConcurrentLruCore class with the specified concurrencyLevel, capacity, equality comparer, item policy and telemetry policy. + /// + /// The concurrency level. + /// The capacity. + /// The equality comparer. + /// The item policy. + /// The telemetry policy. + /// public ConcurrentLruCore( int concurrencyLevel, ICapacityPartition capacity, @@ -96,12 +105,22 @@ public ConcurrentLruCore( /// public Optional> Events => new Optional>(new Proxy(this)); + /// public CachePolicy Policy => CreatePolicy(this); + /// + /// Gets the number of hot items. + /// public int HotCount => Volatile.Read(ref this.counter.hot); + /// + /// Gets the number of warm items. + /// public int WarmCount => Volatile.Read(ref this.counter.warm); + /// + /// Gets the number of cold items. + /// public int ColdCount => Volatile.Read(ref this.counter.cold); /// @@ -335,7 +354,7 @@ public void Trim(int itemCount) // first scan each queue for discardable items and remove them immediately. Note this can remove > itemCount items. int itemsRemoved = this.itemPolicy.CanDiscard() ? TrimAllDiscardedItems() : 0; - TrimLiveItems(itemsRemoved, itemCount, capacity); + TrimLiveItems(itemsRemoved, itemCount); } private void TrimExpired() @@ -346,7 +365,7 @@ private void TrimExpired() } } - protected int TrimAllDiscardedItems() + private int TrimAllDiscardedItems() { int itemsRemoved = 0; @@ -379,7 +398,7 @@ void RemoveDiscardableItems(ConcurrentQueue q, ref int queueCounter) return itemsRemoved; } - private void TrimLiveItems(int itemsRemoved, int itemCount, int capacity) + private void TrimLiveItems(int itemsRemoved, int itemCount) { // If clear is called during trimming, it would be possible to get stuck in an infinite // loop here. Instead quit after n consecutive failed attempts to move warm/hot to cold.