如何测量 .NET 内存缓存 4.0 的当前大小?
Posted
技术标签:
【中文标题】如何测量 .NET 内存缓存 4.0 的当前大小?【英文标题】:How to measure current size of .NET Memory Cache 4.0? 【发布时间】:2014-03-13 22:49:00 【问题描述】:目前我们正在使用 .NET Memory Cache 4.0 来满足缓存要求。 (不是 ASP.NET 缓存,不是任何外部缓存)
查看“.NET Memory Cache 4.0”性能计数器,有关于缓存命中、未命中、条目、修剪等的数据,但与大小无关。
有没有一种方法可以测量/了解生产应用程序使用的缓存的当前大小?
我希望能够在不同的时间点捕获这些数据并获得缓存的平均大小。
【问题讨论】:
这可能有某种关联***.com/questions/1128315/… 感谢链接“核糖”。但我正在寻找一种性能计数器/外部命令行开关(应用程序结构统计)的方式来获取这些数据。不使用内联代码自己测量内存。 另外,到目前为止,我发现进程的“私有工作集 64”已经给出了内存使用量的估计值。这不是最准确的数字,但是我现在能得到的最好的数字。我正在寻找更可靠和准确的指针。 【参考方案1】:这是一个丑陋的实现细节,微软根本不想公开。在 .NET 中测量对象大小通常是不可能的。 MemoryCache 使用一个相当讨厌的后门来实现其内存限制触发器,它使用 CLR 的 DACCESS 组件,实际上是为了帮助实现内存分析器。
您可以使用调试器查看它,因此您不会无法访问它。你只需要编写非常丑陋的代码来挖掘私有字段:
using System;
using System.Reflection;
using System.Runtime.Caching;
public static class MemoryCacheHackExtensions
public static long GetApproximateSize(this MemoryCache cache)
var statsField = typeof(MemoryCache).GetField("_stats", BindingFlags.NonPublic | BindingFlags.Instance);
var statsValue = statsField.GetValue(cache);
var monitorField = statsValue.GetType().GetField("_cacheMemoryMonitor", BindingFlags.NonPublic | BindingFlags.Instance);
var monitorValue = monitorField.GetValue(statsValue);
var sizeField = monitorValue.GetType().GetField("_sizedRef", BindingFlags.NonPublic | BindingFlags.Instance);
var sizeValue = sizeField.GetValue(monitorValue);
var approxProp = sizeValue.GetType().GetProperty("ApproximateSize", BindingFlags.NonPublic | BindingFlags.Instance);
return (long)approxProp.GetValue(sizeValue, null);
似乎在 .NET 4.6.1 上运行良好,未经过广泛测试。了解情况是可以的,只是不要依赖它,因为它可能会因任何 .NET 更新而中断。
【讨论】:
感谢@hans 的回答,但正如我在问题中提到的,我正在寻找一个外部工具来测量这种(非检测代码)类型的 appfabrics-stats 命令行开关,即使我必须在机器上运行它们。我想,作为内存缓存,以及性能计数器没有暴露的事实,采用代码方式可能是衡量大小的唯一方法。我们的代码是一个繁忙的生产代码,我真的不想用反射/快速修复代码戳它,因为我们的应用程序域中的多个层使用各种命名缓存并进行大量读/写。很高兴了解这种方法 这是不可能的,MemoryCache 不会使该测量值在外部可见。您只能在进程中进行。 我的 sizeref 始终为零。我可以在调试模式下看到缓存中的值,但是这段代码总是返回零 在 .net 4.7.2 上,代码中断必须将“_sizedRef”更改为“_sizedRefMultiple”,但我的尺寸为零 gpanagakis 修复将_sizedRef
更改为 _sizedRefMultiple
对我有用。当您得到零时,请等待两分钟,因为这似乎是更新统计信息的轮询间隔。【参考方案2】:
我采用了原始代码并进行了小幅调整,我使用 "_sizedRefMultiple" 而不是 "_sizedRef" 使其适用于 .NET 4.6.
public static class MemoryCacheHackExtensions
public static long GetApproximateSize(this MemoryCache cache)
var statsField = typeof(MemoryCache).GetField("_stats", BindingFlags.NonPublic | BindingFlags.Instance);
var statsValue = statsField.GetValue(cache);
var monitorField = statsValue.GetType().GetField("_cacheMemoryMonitor", BindingFlags.NonPublic | BindingFlags.Instance);
var monitorValue = monitorField.GetValue(statsValue);
var sizeField = monitorValue.GetType().GetField("_sizedRefMultiple", BindingFlags.NonPublic | BindingFlags.Instance);
var sizeValue = sizeField.GetValue(monitorValue);
var approxProp = sizeValue.GetType().GetProperty("ApproximateSize", BindingFlags.NonPublic | BindingFlags.Instance);
return (long)approxProp.GetValue(sizeValue, null);
【讨论】:
我的 sizerefmultiple 始终为零。我可以在调试模式下看到缓存中的值,但是这段代码总是返回零 必须在 .NET 4.5 上使用相同的技巧 ' var sizeVarName = "_sizedRef"; #if NET45 sizeVarName = "_sizedRefMultiple"; #endif '【参考方案3】:作为替代方案,您还可以实现IMemoryCacheManager
接口并将其分配给全局ObjectCache.Host
属性。
这要求您被允许这样做,即您的应用程序中没有其他组件已经这样做(想到 ASP.NET,但我不确定)。就个人而言,我在控制台/Windows 服务应用程序中使用该方法没有任何问题。
另请注意,您只会在大约完整的 GC 之后获得缓存大小,但这与 Hans' approach 应该没有任何不同。
另请注意,以下代码适用于命名的 MemoryCache,即不适用于实例本身。
相当“但是”的观点。但是,它不需要反射。
所以,这里是代码。
public static class MemoryCacheHelper
private static readonly MemoryCacheServiceProvider s_serviceProvider = new MemoryCacheServiceProvider();
static MemoryCacheHelper()
try
ObjectCache.Host = s_serviceProvider;
catch (InvalidOperationException ex)
// ObjectCache.Host can only be set once.
public static MemoryCache Create(string name, NameValueCollection config)
return new MemoryCache(name, config);
// Return approximate cache size and when that value was last determined.
public static Tuple<long, DateTime> GetApproximateSize(string name)
return s_serviceProvider.GetApproximateSize(cache.Name);
private class MemoryCacheServiceProvider : IMemoryCacheManager, IServiceProvider
private readonly object m_lock = new object();
private readonly IDictionary<string, Tuple<long, DateTime>> m_sizes = new Dictionary<string, Tuple<long, DateTime>>();
public Tuple<long, DateTime> GetApproximateSize(string name)
lock (m_lock)
Tuple<long, DateTime> info;
if (m_sizes.TryGetValue(name, out info))
return info;
return null;
void IMemoryCacheManager.UpdateCacheSize(long size, MemoryCache cache)
lock (m_lock)
// The UpdateCacheSize() method will be called based on the configured "pollingInterval"
// for the respective cache. That value defaults to 2 minutes. So this statement doesn't
// fire to often and as a positive side effect we get some sort of "size-heartbeat" which
// might help when troubleshooting.
m_sizes[cache.Name] = Tuple.Create(size, DateTime.UtcNow);
void IMemoryCacheManager.ReleaseCache(MemoryCache cache)
lock (m_lock)
m_sizes.Remove(cache.Name);
object IServiceProvider.GetService(Type serviceType)
if (serviceType == typeof(IMemoryCacheManager))
return this;
return null;
【讨论】:
感谢@christian.K 的回答,但正如我在问题中提到的那样,我正在寻找一个外部工具来测量这种(非检测代码)类型的 appfabrics-stats 命令行开关,即使我必须在机器上运行它们。我想,作为内存缓存,以及性能计数器没有暴露的事实,采用代码方式可能是衡量大小的唯一方法。我们的代码是一个繁忙的生产代码,我真的不想用单个主机戳它,因为我们的应用程序域中的多个层使用各种命名缓存。但很高兴知道这些代码级选项 正如@HansPassant 所说,实际上并没有外部工具。一个可能的选项,尽管相当复杂,是使用类似Microsoft.Runtime.Diagnostics
(基本上是WinDbg的托管版本)并尝试在运行时(和/或从外部您使用此包编码的工具)。当然,您也可以在批处理模式下运行 WinDbg 和脚本来实现相同的目的。我不确定这是否是您在生产中应该做的事情(最终故障排除除外),所以我不会为此提供答案。
这正是我想要的!注意:当我在 LINQPad 中尝试上述代码时,起初它似乎不起作用。那是因为“UpdateCacheSize”被缓存on an interval调用,默认为2分钟。如果您希望看到它更频繁地更新(或者您不耐烦),您必须通过 app.config
或通过构造函数参数更改该间隔,例如:new MemoryCache("foo", new NameValueCollection "PollingInterval", "00:00:01" )
。
当然你必须调用 GC.Collect()
来强制在测试应用程序中进行完整的 GC 才能看到 UpdateCacheSize
被调用,否则它可能永远不会被调用。不过,切勿在实际应用程序中调用 GC.Collect()
。【参考方案4】:
我编写了一个扩展方法,它遍历对象的公共属性并总结大小值。如果它是 IEnumerable 或 Array,它将遍历项目并计算它们的大小。这不是最好的方法,但对我来说足够好,因为我的缓存没有比这更复杂的要求。
注意:每个缓存条目似乎有大约 600 字节的开销,您应该将其添加到计算中。
public static class ObjectMemorySizeCalculator
static int OBJECT_SIZE = IntPtr.Size == 8 ? 24 : 12;
static int POINTER_SIZE = IntPtr.Size;
public static long GetMemorySize(this object obj)
long memorySize = 0;
Type objType = obj.GetType();
if (objType.IsValueType)
memorySize = Marshal.SizeOf(obj);
else if (objType.Equals(typeof(string)))
var str = (string)obj;
memorySize = str.Length * 2 + 6 + OBJECT_SIZE;
else if (objType.IsArray)
var arr = (Array)obj;
var elementType = objType.GetElementType();
if (elementType.IsValueType)
long elementSize = Marshal.SizeOf(elementType);
long elementCount = arr.LongLength;
memorySize += elementSize * elementCount;
else
foreach (var element in arr)
memorySize += element != null ? element.GetMemorySize() + POINTER_SIZE : POINTER_SIZE;
memorySize += OBJECT_SIZE + 40;
else if (obj is IEnumerable)
var enumerable = (IEnumerable)obj;
foreach(var item in enumerable)
var itemType = item.GetType();
memorySize += item != null ? item.GetMemorySize() : 0;
if (itemType.IsClass)
memorySize += POINTER_SIZE;
memorySize += OBJECT_SIZE;
else if (objType.IsClass)
var properties = objType.GetProperties();
foreach (var property in properties)
var valueObject = property.GetValue(obj);
memorySize += valueObject != null ? valueObject.GetMemorySize() : 0;
if (property.GetType().IsClass)
memorySize += POINTER_SIZE;
memorySize += OBJECT_SIZE;
return memorySize;
【讨论】:
【参考方案5】:在尝试获取 MemoryDistributedCache 的大小时,我受到 Hans Passant 的回答的启发。代码如下所示:
public static class MemoryDistributedCacheHackExtensions
public static long GetCacheSize(this MemoryDistributedCache cache)
var memCacheField = typeof(MemoryDistributedCache).GetField("_memCache", BindingFlags.NonPublic | BindingFlags.Instance);
var memCacheValue = memCacheField.GetValue(cache);
var cacheSizeField = memCacheValue.GetType().GetField("_cacheSize", BindingFlags.NonPublic | BindingFlags.Instance);
var cacheSizeValue = cacheSizeField.GetValue(memCacheValue);
return (long)cacheSizeValue;
因此,这不是原始问题的答案。我想把代码放在这里,以防其他人遇到我遇到的同样问题。
【讨论】:
【参考方案6】:从 .NET 4.7 开始,您可以使用 GetLastSize()
https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache.getlastsize?view=dotnet-plat-ext-6.0
【讨论】:
以上是关于如何测量 .NET 内存缓存 4.0 的当前大小?的主要内容,如果未能解决你的问题,请参考以下文章