Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
945 changes: 945 additions & 0 deletions src/Orleans.Core/Caching/ConcurrentLruCache.cs

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions src/Orleans.Core/Caching/Internal/CacheDebugView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Orleans.Caching.Internal;

// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/CacheDebugView.cs
[ExcludeFromCodeCoverage]
internal sealed class CacheDebugView<K, V>
where K : notnull
{
private readonly ConcurrentLruCache<K, V> _cache;

public CacheDebugView(ConcurrentLruCache<K, V> cache)
{
ArgumentNullException.ThrowIfNull(cache);
_cache = cache;
}

public KeyValuePair<K, V>[] Items
{
get
{
var items = new KeyValuePair<K, V>[_cache.Count];

var index = 0;
foreach (var kvp in _cache)
{
items[index++] = kvp;
}
return items;
}
}

public ICacheMetrics? Metrics => _cache.Metrics;
}
73 changes: 73 additions & 0 deletions src/Orleans.Core/Caching/Internal/CapacityPartition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Diagnostics;

namespace Orleans.Caching.Internal;

/// <summary>
/// A capacity partitioning scheme that favors frequently accessed items by allocating 80%
/// capacity to the warm queue.
/// </summary>
// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Lru/FavorWarmPartition.cs
[DebuggerDisplay("{Hot}/{Warm}/{Cold}")]
internal readonly struct CapacityPartition
{
/// <summary>
/// Default to 80% capacity allocated to warm queue, 20% split equally for hot and cold.
/// This favors frequently accessed items.
/// </summary>
public const double DefaultWarmRatio = 0.8;

/// <summary>
/// Initializes a new instance of the CapacityPartition class with the specified capacity and the default warm ratio.
/// </summary>
/// <param name="totalCapacity">The total capacity.</param>
public CapacityPartition(int totalCapacity)
: this(totalCapacity, DefaultWarmRatio)
{
}

/// <summary>
/// Initializes a new instance of the CapacityPartition class with the specified capacity and warm ratio.
/// </summary>
/// <param name="totalCapacity">The total capacity.</param>
/// <param name="warmRatio">The ratio of warm items to hot and cold items.</param>
public CapacityPartition(int totalCapacity, double warmRatio)
{
ArgumentOutOfRangeException.ThrowIfLessThan(totalCapacity, 3);
ArgumentOutOfRangeException.ThrowIfLessThan(warmRatio, 0.0);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(warmRatio, 1.0);

var (hot, warm, cold) = ComputeQueueCapacity(totalCapacity, warmRatio);
Debug.Assert(cold >= 1);
Debug.Assert(warm >= 1);
Debug.Assert(hot >= 1);
Hot = hot;
Warm = warm;
Cold = cold;
}

public int Cold { get; }

public int Warm { get; }

public int Hot { get; }

private static (int hot, int warm, int cold) ComputeQueueCapacity(int capacity, double warmRatio)
{
var warm2 = (int)(capacity * warmRatio);
var hot2 = (capacity - warm2) / 2;

if (hot2 < 1)
{
hot2 = 1;
}

var cold2 = hot2;

var overflow = warm2 + hot2 + cold2 - capacity;
warm2 -= overflow;

return (hot2, warm2, cold2);
}
}
71 changes: 71 additions & 0 deletions src/Orleans.Core/Caching/Internal/Counter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#nullable enable
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/

using Orleans;

namespace Orleans.Caching.Internal;

/// <summary>
/// A thread-safe counter suitable for high throughput counting across many concurrent threads.
/// </summary>
/// Based on the LongAdder class by Doug Lea.
// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Counters/Counter.cs
internal sealed class Counter : Striped64
{
/// <summary>
/// Creates a new Counter with an initial sum of zero.
/// </summary>
public Counter() { }

/// <summary>
/// Computes the current count.
/// </summary>
/// <returns>The current sum.</returns>
public long Count()
{
var @as = cells; Cell a;
var sum = @base.VolatileRead();
if (@as != null)
{
for (var i = 0; i < @as.Length; ++i)
{
if ((a = @as[i]) != null)
sum += a.Value.VolatileRead();
}
}
return sum;
}

/// <summary>
/// Increment by 1.
/// </summary>
public void Increment()
{
Add(1L);
}

/// <summary>
/// Adds the specified value.
/// </summary>
/// <param name="value">The value to add.</param>
public void Add(long value)
{
Cell[]? @as;
long b, v;
int m;
Cell a;
if ((@as = cells) != null || [email protected](b = @base.VolatileRead(), b + value))
{
var uncontended = true;
if (@as == null || (m = @as.Length - 1) < 0 || (a = @as[GetProbe() & m]) == null || !(uncontended = a.Value.CompareAndSwap(v = a.Value.VolatileRead(), v + value)))
{
LongAccumulate(value, uncontended);
}
}
}
}
40 changes: 40 additions & 0 deletions src/Orleans.Core/Caching/Internal/ICacheMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Orleans.Caching.Internal;

/// <summary>
/// Represents cache metrics collected over the lifetime of the cache.
/// If metrics are disabled.
/// </summary>
// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/ICacheMetrics.cs?plain=1#L8C22-L8C35
internal interface ICacheMetrics
{
/// <summary>
/// Gets the ratio of hits to misses, where a value of 1 indicates 100% hits.
/// </summary>
double HitRatio { get; }

/// <summary>
/// Gets the total number of requests made to the cache.
/// </summary>
long Total { get; }

/// <summary>
/// Gets the total number of cache hits.
/// </summary>
long Hits { get; }

/// <summary>
/// Gets the total number of cache misses.
/// </summary>
long Misses { get; }

/// <summary>
/// Gets the total number of evicted items.
/// </summary>
long Evicted { get; }

/// <summary>
/// Gets the total number of updated items.
/// </summary>
long Updated { get; }
}
33 changes: 33 additions & 0 deletions src/Orleans.Core/Caching/Internal/PaddedLong.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using System.Threading;

namespace Orleans.Caching.Internal;

/// <summary>
/// A long value padded by the size of a CPU cache line to mitigate false sharing.
/// </summary>
// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Counters/PaddedLong.cs
[StructLayout(LayoutKind.Explicit, Size = 2 * Padding.CACHE_LINE_SIZE)] // padding before/between/after fields
internal struct PaddedLong
{
/// <summary>
/// The value.
/// </summary>
[FieldOffset(Padding.CACHE_LINE_SIZE)] public long Value;

/// <summary>
/// Reads the value of the field, and on systems that require it inserts a memory barrier to
/// prevent reordering of memory operations.
/// </summary>
/// <returns>The value that was read.</returns>
public long VolatileRead() => Volatile.Read(ref Value);

/// <summary>
/// Compares the current value with an expected value, if they are equal replaces the current value.
/// </summary>
/// <param name="expected">The expected value.</param>
/// <param name="updated">The updated value.</param>
/// <returns>True if the value is updated, otherwise false.</returns>
public bool CompareAndSwap(long expected, long updated) => Interlocked.CompareExchange(ref Value, updated, expected) == expected;
}
15 changes: 15 additions & 0 deletions src/Orleans.Core/Caching/Internal/PaddedQueueCount.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Orleans.Caching.Internal;

// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Lru/PaddedQueueCount.cs
[DebuggerDisplay("Hot = {Hot}, Warm = {Warm}, Cold = {Cold}")]
[StructLayout(LayoutKind.Explicit, Size = 4 * Padding.CACHE_LINE_SIZE)] // padding before/between/after fields
internal struct PaddedQueueCount
{
[FieldOffset(1 * Padding.CACHE_LINE_SIZE)] public int Hot;
[FieldOffset(2 * Padding.CACHE_LINE_SIZE)] public int Warm;
[FieldOffset(3 * Padding.CACHE_LINE_SIZE)] public int Cold;
}
12 changes: 12 additions & 0 deletions src/Orleans.Core/Caching/Internal/Padding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Orleans.Caching.Internal;

// Derived from BitFaster.Caching by Alex Peck
// https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Padding.cs
internal static class Padding
{
#if TARGET_ARM64 || TARGET_LOONGARCH64
internal const int CACHE_LINE_SIZE = 128;
#else
internal const int CACHE_LINE_SIZE = 64;
#endif
}
Loading