Skip to content

Commit

Permalink
More optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
pentp committed Oct 20, 2021
1 parent 10b1015 commit b4a1843
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 79 deletions.
51 changes: 34 additions & 17 deletions src/libraries/Microsoft.Extensions.Caching.Memory/src/CacheEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Caching.Memory
internal sealed partial class CacheEntry : ICacheEntry
{
private static readonly Action<object> ExpirationCallback = ExpirationTokensExpired;
private static readonly AsyncLocal<CacheEntry> _current = new AsyncLocal<CacheEntry>();

private readonly MemoryCache _cache;

Expand All @@ -29,17 +30,26 @@ internal sealed partial class CacheEntry : ICacheEntry
private bool _isExpired;
private bool _isValueSet;
private byte _evictionReason;
private byte _priority;
private byte _priority = (byte)CacheItemPriority.Normal;

internal CacheEntry(object key, MemoryCache memoryCache)
{
Key = key ?? throw new ArgumentNullException(nameof(key));
_cache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
_previous = memoryCache.TrackLinkedCacheEntries ? CacheEntryHelper.EnterScope(this) : null;
_priority = (byte)CacheItemPriority.Normal;
if (memoryCache.TrackLinkedCacheEntries)
{
var holder = _current;
_previous = holder.Value;
holder.Value = this;
}
}

internal bool HasAbsoluteExpiration => Unsafe.As<DateTime, long>(ref _absoluteExpiration) != 0;
// internal for testing
internal static CacheEntry Current => _current.Value;

private ref ulong RawAbsoluteExpiration => ref Unsafe.As<DateTime, ulong>(ref _absoluteExpiration);

internal bool HasAbsoluteExpiration => RawAbsoluteExpiration != 0;

internal DateTime AbsoluteExpiration => _absoluteExpiration;

Expand Down Expand Up @@ -69,8 +79,9 @@ internal void SetAbsoluteExpirationUtc(DateTime value)
}
else
{
_absoluteExpiration = value.GetValueOrDefault().UtcDateTime;
_absoluteExpirationOffsetMinutes = (short)(value.GetValueOrDefault().Offset.Ticks / TimeSpan.TicksPerMinute);
DateTimeOffset expiration = value.GetValueOrDefault();
_absoluteExpiration = expiration.UtcDateTime;
_absoluteExpirationOffsetMinutes = (short)(expiration.Offset.Ticks / TimeSpan.TicksPerMinute);
}
}
}
Expand Down Expand Up @@ -154,7 +165,7 @@ public TimeSpan? SlidingExpiration
}
}

public object Key { get; private set; }
public object Key { get; }

public object Value
{
Expand All @@ -178,7 +189,8 @@ public void Dispose()

if (_cache.TrackLinkedCacheEntries)
{
CacheEntryHelper.ExitScope(this, _previous);
Debug.Assert(_current.Value == this, "Entries disposed in invalid order");
_current.Value = _previous;
}

// Don't commit or propagate options if the CacheEntry Value was never set.
Expand All @@ -188,9 +200,15 @@ public void Dispose()
{
_cache.SetEntry(this);

if (_previous != null && CanPropagateOptions())
CacheEntry parent = _previous;
if (parent != null)
{
PropagateOptions(_previous);
if (HasAbsoluteExpiration && (!parent.HasAbsoluteExpiration || RawAbsoluteExpiration < parent.RawAbsoluteExpiration))
{
parent._absoluteExpiration = _absoluteExpiration;
parent._absoluteExpirationOffsetMinutes = _absoluteExpirationOffsetMinutes;
}
_tokens?.PropagateTokens(parent);
}
}

Expand Down Expand Up @@ -258,26 +276,25 @@ private static void ExpirationTokensExpired(object obj)

internal void InvokeEvictionCallbacks() => _tokens?.InvokeEvictionCallbacks(this);

// this simple check very often allows us to avoid expensive call to PropagateOptions(CacheEntryHelper.Current)
[MethodImpl(MethodImplOptions.AggressiveInlining)] // added based on profiling
internal bool CanPropagateOptions() => (_tokens != null && _tokens.CanPropagateTokens()) || HasAbsoluteExpiration;
internal bool CanPropagateTokens() => _tokens != null && _tokens.CanPropagateTokens();

internal void PropagateOptions(CacheEntry parent)
internal void PropagateOptionsToCurrent()
{
CacheEntry parent = _current.Value;
if (parent == null)
{
return;
}

// Copy expiration tokens and AbsoluteExpiration to the cache entries hierarchy.
// We do this regardless of it gets cached because the tokens are associated with the value we'll return.
_tokens?.PropagateTokens(parent);

if (HasAbsoluteExpiration && (!parent.HasAbsoluteExpiration || _absoluteExpiration < parent._absoluteExpiration))
if (HasAbsoluteExpiration && (!parent.HasAbsoluteExpiration || RawAbsoluteExpiration < parent.RawAbsoluteExpiration))
{
parent._absoluteExpiration = _absoluteExpiration;
parent._absoluteExpirationOffsetMinutes = _absoluteExpirationOffsetMinutes;
}

_tokens?.PropagateTokens(parent);
}

private CacheEntryTokens GetOrCreateTokens()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory
public int Count => _entries.Count;

// internal for testing
internal long Size { get => Interlocked.Read(ref _cacheSize); }
internal long Size => Volatile.Read(ref _cacheSize);

internal bool TrackLinkedCacheEntries { get; }

Expand All @@ -97,7 +97,7 @@ internal void SetEntry(CacheEntry entry)
return;
}

if (_options.SizeLimit.HasValue && entry.Size < 0)
if (_options.HasSizeLimit && entry.Size < 0)
{
throw new InvalidOperationException(SR.Format(SR.CacheEntryHasEmptySize, nameof(entry.Size), nameof(_options.SizeLimit)));
}
Expand Down Expand Up @@ -147,7 +147,7 @@ internal void SetEntry(CacheEntry entry)

if (entryAdded)
{
if (_options.SizeLimit.HasValue)
if (_options.HasSizeLimit)
{
// The prior entry was removed, decrease the by the prior entry's size
Interlocked.Add(ref _cacheSize, -priorEntry.Size);
Expand All @@ -168,7 +168,7 @@ internal void SetEntry(CacheEntry entry)
}
else
{
if (_options.SizeLimit.HasValue)
if (_options.HasSizeLimit)
{
// Entry could not be added, reset cache size
Interlocked.Add(ref _cacheSize, -entry.Size);
Expand Down Expand Up @@ -201,20 +201,21 @@ public bool TryGetValue(object key, out object result)

DateTime utcNow = UtcNow;

if (_entries.TryGetValue(key, out CacheEntry entry))
if (_entries.TryGetValue(key, out CacheEntry tmp))
{
CacheEntry entry = tmp;
// Check if expired due to expiration tokens, timers, etc. and if so, remove it.
// Allow a stale Replaced value to be returned due to concurrent calls to SetExpired during SetEntry.
if (!entry.CheckExpired(utcNow) || entry.EvictionReason == EvictionReason.Replaced)
{
entry.LastAccessed = utcNow;
result = entry.Value;

if (TrackLinkedCacheEntries && entry.CanPropagateOptions())
if (TrackLinkedCacheEntries && (entry.CanPropagateTokens() || entry.HasAbsoluteExpiration))
{
// When this entry is retrieved in the scope of creating another entry,
// that entry needs a copy of these expiration tokens.
entry.PropagateOptions(CacheEntryHelper.Current);
entry.PropagateOptionsToCurrent();
}

StartScanForExpiredItemsIfNeeded(utcNow);
Expand Down Expand Up @@ -242,7 +243,7 @@ public void Remove(object key)
CheckDisposed();
if (_entries.TryRemove(key, out CacheEntry entry))
{
if (_options.SizeLimit.HasValue)
if (_options.HasSizeLimit)
{
Interlocked.Add(ref _cacheSize, -entry.Size);
}
Expand All @@ -258,7 +259,7 @@ private void RemoveEntry(CacheEntry entry)
{
if (EntriesCollection.Remove(new KeyValuePair<object, CacheEntry>(entry.Key, entry)))
{
if (_options.SizeLimit.HasValue)
if (_options.HasSizeLimit)
{
Interlocked.Add(ref _cacheSize, -entry.Size);
}
Expand Down Expand Up @@ -308,26 +309,29 @@ private void ScanForExpiredItems()

private bool UpdateCacheSizeExceedsCapacity(CacheEntry entry)
{
if (!_options.SizeLimit.HasValue)
long sizeLimit = _options.SizeLimitValue;
if (sizeLimit < 0)
{
return false;
}

long sizeRead = Volatile.Read(ref _cacheSize);
for (int i = 0; i < 100; i++)
{
long sizeRead = Interlocked.Read(ref _cacheSize);
long newSize = sizeRead + entry.Size;

if (newSize < 0 || newSize > _options.SizeLimit)
if ((ulong)newSize > (ulong)sizeLimit)
{
// Overflow occurred, return true without updating the cache size
return true;
}

if (sizeRead == Interlocked.CompareExchange(ref _cacheSize, newSize, sizeRead))
long original = Interlocked.CompareExchange(ref _cacheSize, newSize, sizeRead);
if (sizeRead == original)
{
return false;
}
sizeRead = original;
}

return true;
Expand All @@ -344,19 +348,23 @@ private void TriggerOvercapacityCompaction()

private void OvercapacityCompaction()
{
long currentSize = Interlocked.Read(ref _cacheSize);
long currentSize = Volatile.Read(ref _cacheSize);

if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug($"Overcapacity compaction executing. Current size {currentSize}");

double? lowWatermark = _options.SizeLimit * (1 - _options.CompactionPercentage);
if (currentSize > lowWatermark)
long sizeLimit = _options.SizeLimitValue;
if (sizeLimit >= 0)
{
Compact(currentSize - (long)lowWatermark, entry => entry.Size);
long lowWatermark = sizeLimit - (long)(sizeLimit * _options.CompactionPercentage);
if (currentSize > lowWatermark)
{
Compact(currentSize - lowWatermark, entry => entry.Size);
}
}

if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug($"Overcapacity compaction executed. New size {Interlocked.Read(ref _cacheSize)}");
_logger.LogDebug($"Overcapacity compaction executed. New size {Volatile.Read(ref _cacheSize)}");
}

/// Remove at least the given percentage (0.10 for 10%) of the total entries (or estimated memory?), according to the following policy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.Caching.Memory
{
public class MemoryCacheOptions : IOptions<MemoryCacheOptions>
{
private long? _sizeLimit;
private long _sizeLimit = -1;
private double _compactionPercentage = 0.05;

public ISystemClock Clock { get; set; }
Expand All @@ -19,20 +19,24 @@ public class MemoryCacheOptions : IOptions<MemoryCacheOptions>
/// </summary>
public TimeSpan ExpirationScanFrequency { get; set; } = TimeSpan.FromMinutes(1);

internal bool HasSizeLimit => _sizeLimit >= 0;

internal long SizeLimitValue => _sizeLimit;

/// <summary>
/// Gets or sets the maximum size of the cache.
/// </summary>
public long? SizeLimit
{
get => _sizeLimit;
get => _sizeLimit < 0 ? null : _sizeLimit;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(value)} must be non-negative.");
}

_sizeLimit = value;
_sizeLimit = value ?? -1;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Caching.Distributed
{
public class MemoryDistributedCache : IDistributedCache
{
private readonly IMemoryCache _memCache;
private readonly MemoryCache _memCache;

public MemoryDistributedCache(IOptions<MemoryDistributedCacheOptions> optionsAccessor)
: this(optionsAccessor, NullLoggerFactory.Instance) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ public void GetWithImplicitLinkPopulatesExpirationTokens(bool trackLinkedCacheEn
string key = "myKey";
string key1 = "myKey1";

Assert.Null(CacheEntryHelper.Current);
Assert.Null(CacheEntry.Current);

ICacheEntry entry;
using (entry = cache.CreateEntry(key))
Expand All @@ -351,7 +351,7 @@ public void GetWithImplicitLinkPopulatesExpirationTokens(bool trackLinkedCacheEn
cache.Set(key1, obj, new MemoryCacheEntryOptions().AddExpirationToken(expirationToken));
}

Assert.Null(CacheEntryHelper.Current);
Assert.Null(CacheEntry.Current);

Assert.Equal(trackLinkedCacheEntries ? 1 : 0, entry.ExpirationTokens.Count);
Assert.Null(entry.AbsoluteExpiration);
Expand All @@ -367,7 +367,7 @@ public void LinkContextsCanNest(bool trackLinkedCacheEntries)
string key = "myKey";
string key1 = "myKey1";

Assert.Null(CacheEntryHelper.Current);
Assert.Null(CacheEntry.Current);

ICacheEntry entry;
ICacheEntry entry1;
Expand All @@ -387,7 +387,7 @@ public void LinkContextsCanNest(bool trackLinkedCacheEntries)
VerifyCurrentEntry(trackLinkedCacheEntries, entry);
}

Assert.Null(CacheEntryHelper.Current);
Assert.Null(CacheEntry.Current);

Assert.Single(entry1.ExpirationTokens);
Assert.Null(entry1.AbsoluteExpiration);
Expand Down Expand Up @@ -543,11 +543,11 @@ private static void VerifyCurrentEntry(bool trackLinkedCacheEntries, ICacheEntry
{
if (trackLinkedCacheEntries)
{
Assert.Same(entry, CacheEntryHelper.Current);
Assert.Same(entry, CacheEntry.Current);
}
else
{
Assert.Null(CacheEntryHelper.Current);
Assert.Null(CacheEntry.Current);
}
}
}
Expand Down
Loading

0 comments on commit b4a1843

Please sign in to comment.