如何衡量.NET Memory Cache 4.0的当前大小?

时间:2014-03-13 22:49:01

标签: c# .net

目前我们正在使用.NET Memory Cache 4.0来满足缓存要求。 (不是ASP.NET缓存,不是任何外部缓存)

查看“.NET内存缓存4.0”性能计数器,有关于缓存命中,未命中,条目,修剪等的数据,但与尺寸无关。

是否有一种方法可以衡量/了解生产应用程序使用的缓存的当前大小

我希望能够在不同的时间点捕获这些数据,并获得缓存的平均大小。

4 个答案:

答案 0 :(得分:22)

这是一个丑陋的实现细节,微软根本不想公开。在.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更新。

答案 1 :(得分:7)

我使用原始代码并进行了微调,我使用了" _sizedRefMultiple" 而不是" _sizedRef" 使其适用于 .NET 4.6

Public Sub test3()

Dim i As Integer

With Sheet10

.Cells(3, 13).Formula = "=0"

For i = 1 To 100
If .Cells(1, 22 * i - 8) <> 0 Then

.Cells(3, 13).Formula = .Cells(3, 13).Formula & "+" & .Cells(3, 22 * i + 2).Address

 End If

 If .Cells(1, 22 * i + 3) <> 0 Then

.Cells(3, 13).Formula = .Cells(3, 13).Formula & "+" & .Cells(3, 22 * i + 13).Address

End If

Next i

End With

End Sub

答案 2 :(得分:2)

作为替代方案,您还可以实现IMemoryCacheManager接口并将其分配给全局ObjectCache.Host属性。 这要求您可以这样做,即您的应用程序中没有其他组件已经这样做了(ASP.NET会想到,但我不确定)。就个人而言,我在控制台/ Windows服务应用程序中使用该方法没有问题。

另请注意,您只能在完整GC之后获得缓存大小,但与Hans' approach不应该有任何不同。

另请注意,以下代码适用于命名的MemoryCaches,即不在实例本身上。

相当一种观点“但是”。但是,它不需要反思。

所以,这是代码。

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;
        }
    }

答案 3 :(得分:0)

我写了一个扩展方法,该方法迭代对象的公共属性并总结大小值。如果它是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;
        }