-
Notifications
You must be signed in to change notification settings - Fork 570
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[C#] MemOnlyCache sample: improved size tracking, including read cache (
#800) * Improved cache size tracking, including read cache * Rename MemOnlyCache to ResizableCacheStore. * update doc * fixes * Updates
- Loading branch information
Showing
11 changed files
with
257 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. | ||
|
||
using FASTER.core; | ||
using System; | ||
|
||
namespace ResizableCacheStore | ||
{ | ||
/// <summary> | ||
/// Cache size tracker | ||
/// </summary> | ||
public class CacheSizeTracker | ||
{ | ||
readonly FasterKV<CacheKey, CacheValue> store; | ||
|
||
/// <summary> | ||
/// Total size (bytes) used by FASTER including index and log | ||
/// </summary> | ||
public long TotalSizeBytes => | ||
IndexSizeBytes + | ||
mainLog.TotalSizeBytes + | ||
(readCache != null ? readCache.TotalSizeBytes : 0); | ||
|
||
public long IndexSizeBytes => | ||
store.IndexSize * 64 + | ||
store.OverflowBucketCount * 64; | ||
|
||
public long LogSizeBytes => mainLog.TotalSizeBytes; | ||
public long ReadCacheSizeBytes => readCache != null ? readCache.TotalSizeBytes : 0; | ||
|
||
readonly LogSizeTracker<CacheKey, CacheValue> mainLog; | ||
readonly LogSizeTracker<CacheKey, CacheValue> readCache; | ||
|
||
public void PrintStats() | ||
{ | ||
Console.WriteLine("Sizes: [store]: {0,8:N2}KB [index]: {1,9:N2}KB [hylog]: {2,8:N2}KB ({3,7} objs) [rcach]: {4,9:N2}KB ({5,7} objs)", | ||
TotalSizeBytes / 1024.0, | ||
IndexSizeBytes / 1024.0, | ||
LogSizeBytes / 1024.0, | ||
mainLog.NumRecords, | ||
ReadCacheSizeBytes / 1024.0, | ||
readCache != null ? readCache.NumRecords : 0 | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Class to track and update cache size | ||
/// </summary> | ||
/// <param name="store">FASTER store instance</param> | ||
/// <param name="targetMemoryBytes">Initial target memory size of FASTER in bytes</param> | ||
public CacheSizeTracker(FasterKV<CacheKey, CacheValue> store, long targetMemoryBytes = long.MaxValue) | ||
{ | ||
this.store = store; | ||
this.mainLog = new LogSizeTracker<CacheKey, CacheValue>(store.Log, "mnlog"); | ||
if (store.ReadCache != null) | ||
this.readCache = new LogSizeTracker<CacheKey, CacheValue>(store.ReadCache, "readc"); | ||
|
||
if (targetMemoryBytes < long.MaxValue) | ||
{ | ||
Console.WriteLine("**** Setting initial target memory: {0,11:N2}KB", targetMemoryBytes / 1024.0); | ||
SetTargetSizeBytes(targetMemoryBytes); | ||
} | ||
|
||
PrintStats(); | ||
} | ||
|
||
/// <summary> | ||
/// Set target total memory size (in bytes) for the FASTER store | ||
/// </summary> | ||
/// <param name="newTargetSize">Target size</param> | ||
public void SetTargetSizeBytes(long newTargetSize) | ||
{ | ||
// In this sample, we split the residual space equally between the log and the read cache | ||
long residual = newTargetSize - IndexSizeBytes; | ||
|
||
if (residual > 0) | ||
{ | ||
if (readCache == null) | ||
mainLog.SetTargetSizeBytes(residual); | ||
else | ||
{ | ||
mainLog.SetTargetSizeBytes(residual / 2); | ||
readCache.SetTargetSizeBytes(residual / 2); | ||
} | ||
} | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Add to the tracked size of FASTER. This is called by IFunctions as well as the subscriber to evictions (OnNext) | ||
/// </summary> | ||
/// <param name="size"></param> | ||
public void AddTrackedSize(int size, bool isReadCache = false) | ||
{ | ||
if (isReadCache) | ||
readCache.AddTrackedSize(size); | ||
else | ||
mainLog.AddTrackedSize(size); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. | ||
|
||
namespace ResizableCacheStore | ||
{ | ||
public interface ISizeTracker | ||
{ | ||
int GetSize { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. | ||
|
||
using FASTER.core; | ||
using System; | ||
using System.Threading; | ||
|
||
namespace ResizableCacheStore | ||
{ | ||
public class LogSizeTracker<TCacheKey, TCacheValue> : IObserver<IFasterScanIterator<TCacheKey, TCacheValue>> | ||
where TCacheKey : ISizeTracker | ||
where TCacheValue : ISizeTracker | ||
{ | ||
readonly string name; | ||
|
||
/// <summary> | ||
/// Number of records in the log | ||
/// </summary> | ||
public int NumRecords; | ||
|
||
/// <summary> | ||
/// Total size occupied by log, including heap | ||
/// </summary> | ||
public long TotalSizeBytes => Log.MemorySizeBytes + heapSize; | ||
|
||
/// <summary> | ||
/// Target size request for FASTER | ||
/// </summary> | ||
public long TargetSizeBytes { get; private set; } | ||
|
||
|
||
int heapSize; | ||
readonly LogAccessor<TCacheKey, TCacheValue> Log; | ||
|
||
public LogSizeTracker(LogAccessor<TCacheKey, TCacheValue> log, string name) | ||
{ | ||
this.name = name; | ||
Log = log; | ||
|
||
// Register subscriber to receive notifications of log evictions from memory | ||
Log.SubscribeEvictions(this); | ||
} | ||
|
||
/// <summary> | ||
/// Add to the tracked size of FASTER. This is called by IFunctions as well as the subscriber to evictions (OnNext) | ||
/// </summary> | ||
/// <param name="size"></param> | ||
public void AddTrackedSize(int size) | ||
{ | ||
Interlocked.Add(ref heapSize, size); | ||
if (size > 0) Interlocked.Increment(ref NumRecords); | ||
else Interlocked.Decrement(ref NumRecords); | ||
} | ||
|
||
/// <summary> | ||
/// Set target total memory size (in bytes) for the FASTER store | ||
/// </summary> | ||
/// <param name="newTargetSize">Target size</param> | ||
public void SetTargetSizeBytes(long newTargetSize) | ||
{ | ||
TargetSizeBytes = newTargetSize; | ||
AdjustAllocation(); | ||
} | ||
public void OnNext(IFasterScanIterator<TCacheKey, TCacheValue> iter) | ||
{ | ||
int size = 0; | ||
int count = 0; | ||
while (iter.GetNext(out RecordInfo info, out TCacheKey key, out TCacheValue value)) | ||
{ | ||
size += key.GetSize; | ||
count++; | ||
if (!info.Tombstone) // ignore deleted values being evicted (they are accounted for by ConcurrentDeleter) | ||
size += value.GetSize; | ||
} | ||
Interlocked.Add(ref heapSize, -size); | ||
Interlocked.Add(ref NumRecords, -count); | ||
AdjustAllocation(); | ||
} | ||
|
||
public void OnCompleted() { } | ||
|
||
public void OnError(Exception error) { } | ||
|
||
void AdjustAllocation() | ||
{ | ||
const long Delta = 1L << 15; | ||
if (TotalSizeBytes > TargetSizeBytes + Delta) | ||
{ | ||
while (TotalSizeBytes > TargetSizeBytes + Delta) | ||
{ | ||
if (Log.AllocatedPageCount > Log.BufferSize - Log.EmptyPageCount + 1) | ||
{ | ||
// Console.WriteLine($"{name}: {Log.EmptyPageCount} (wait++)"); | ||
return; // wait for allocation to stabilize | ||
} | ||
Log.EmptyPageCount++; | ||
// Console.WriteLine($"{name}: {Log.EmptyPageCount} (++)"); | ||
} | ||
} | ||
else if (TotalSizeBytes < TargetSizeBytes - Delta) | ||
{ | ||
while (TotalSizeBytes < TargetSizeBytes - Delta) | ||
{ | ||
if (Log.AllocatedPageCount < Log.BufferSize - Log.EmptyPageCount - 1) | ||
{ | ||
// Console.WriteLine($"{name}: {Log.EmptyPageCount} (wait--)"); | ||
return; // wait for allocation to stabilize | ||
} | ||
Log.EmptyPageCount--; | ||
// Console.WriteLine($"{name}: {Log.EmptyPageCount} (--)"); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.