MemoryCache 不遵守配置中的内存限制 [英] MemoryCache does not obey memory limits in configuration

查看:35
本文介绍了MemoryCache 不遵守配置中的内存限制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 .NET 4.0 MemoryCache 类并试图限制最大缓存大小,但在我的测试中,缓存似乎并未实际遵守限制.

我使用的设置,根据MSDN,应该限制缓存大小:

<块引用>

  1. CacheMemoryLimitMegabytes:对象实例可以增长到的最大内存大小(以兆字节为单位)."
  2. PhysicalMemoryLimitPercentage: "缓存可以使用的物理内存的百分比,表示为1到100的整数值.默认为零,表示MemoryCachestrong> 实例根据计算机上安装的内存量管理自己的内存1."1. 这并不完全正确——任何低于 4 的值都将被忽略并替换为 4.

我知道这些值是近似值而不是硬性限制,因为清除缓存的线程每 x 秒触发一次,并且还取决于轮询间隔和其他未记录的变量.然而,即使考虑到这些差异,在将 CacheMemoryLimitMegabytesPhysicalMemoryLimitPercentage 一起或单独设置在一个测试应用程序.可以肯定的是,我对每个测试进行了 10 次并计算了平均数字.

这些是在具有 3GB RAM 的 32 位 Windows 7 PC 上测试以下示例代码的结果.缓存的大小是在每次测试第一次调用 CacheItemRemoved() 之后获取的.(我知道缓存的实际大小会比这个大)

MemLimitMB MemLimitPct AVG 缓存 MB 第一次到期1 不适用 842 不适用 843 不适用 846 不适用 84不适用 1 84不适用 4 84不适用 10 8410 20 8110 30 8110 39 8210 40 7910 49 14610 50 15210 60 21210 70 33210 80 42910 100 535100 39 81500 39 79900 39 831900 39 84900 41 81900 46 84900 49 约 1.8 GB在任务管理器中没有内存错误200 49 156100 49 1532000 60 2145 60 786 60 767 100 8210 100 541

这是测试应用程序:

使用系统;使用 System.Collections.Generic;使用 System.Collections.Specialized;使用 System.Linq;使用 System.Runtime.Caching;使用 System.Text;命名空间 FinalCacheTest{内部类缓存{私有对象状态锁 = 新对象();私人 int ItemCount;私人长尺寸;私有 MemoryCache MemCache;私有 CacheItemPolicy CIPOL = 新的 CacheItemPolicy();公共缓存(长缓存大小){CIPOL.RemovedCallback = new CacheEntryRemovedCallback(CacheItemRemoved);NameValueCollection CacheSettings = new NameValueCollection(3);CacheSettings.Add("CacheMemoryLimitMegabytes", Convert.ToString(CacheSize));CacheSettings.Add("physicalMemoryLimitPercentage", Convert.ToString(49));//在这里设置%CacheSettings.Add("pollingInterval", Convert.ToString("00:00:10"));MemCache = new MemoryCache("TestCache", CacheSettings);}public void AddItem(字符串名称,字符串值){CacheItem CI = new CacheItem(Name, Value);MemCache.Add(CI, CIPOL);锁(Statlock){ItemCount++;大小 = 大小 + (Name.Length + Value.Length * 2);}}public void CacheItemRemoved(CacheEntryRemovedArguments Args){Console.WriteLine("缓存包含 {0} 个项目.大小为 {1} 个字节", ItemCount, size);锁(Statlock){ItemCount--;大小 = 大小 - 108;}Console.ReadKey();}}}命名空间 FinalCacheTest{内部类程序{私有静态无效主(字符串 [] args){int MaxAdds = 5000000;缓存 MyCache = 新缓存(1);//设置 CacheMemoryLimitMegabytesfor (int i = 0; i 

为什么 MemoryCache 不遵守配置的内存限制?

解决方案

哇,所以我花了太多时间在带有反射器的 CLR 中挖掘,但我想我终于对这里发生的事情有了很好的把握.

设置被正确读取,但 CLR 本身似乎存在一个根深蒂固的问题,看起来它会使内存限制设置基本上无用.

以下代码反映在 System.Runtime.Caching DLL 之外,用于 CacheMemoryMonitor 类(有一个类似的类可以监控物理内存并处理其他设置,但这是更重要的一个):

protected override int GetCurrentPressure(){int num = GC.CollectionCount(2);SRef ref2 = this._sizedRef;if ((num != this._gen2Count) && (ref2 != null)){this._gen2Count = num;this._idx ^= 1;this._cacheSizeSampleTimes[this._idx] = DateTime.UtcNow;this._cacheSizeSamples[this._idx] = ref2.ApproximateSize;IMemoryCacheManager 管理器 = s_memoryCacheManager;如果(经理!= null){manager.UpdateCacheSize(this._cacheSizeSamples[this._idx], this._memoryCache);}}如果(this._memoryLimit <= 0L){返回0;}long num2 = this._cacheSizeSamples[this._idx];if (num2 > this._memoryLimit){num2 = this._memoryLimit;}return (int) ((num2 * 100L)/this._memoryLimit);}

您可能会注意到的第一件事是,它甚至在 Gen2 垃圾回收之后才尝试查看缓存的大小,而只是回退到 cacheSizeSamples 中现有存储的大小值.因此,您将永远无法正确击中目标,但如果其他方法奏效,我们至少可以在遇到真正麻烦之前进行尺寸测量.

因此假设发生了 Gen2 GC,我们遇到了问题 2,即 ref2.ApproximateSize 在实际逼近缓存大小方面做得很糟糕.通过 CLR 垃圾我发现这是一个 System.SizedReference,这就是它为获取值所做的事情(IntPtr 是 MemoryCache 对象本身的句柄):

[SecurityCritical][MethodImpl(MethodImplOptions.InternalCall)]private static extern long GetApproximateSizeOfSizedRef(IntPtr h);

我假设 extern 声明意味着它此时进入非托管窗口区域,我不知道如何开始找出它在那里做什么.从我观察到的情况来看,虽然它在尝试近似整体事物的大小方面做得很糟糕.

第三个值得注意的事情是对 manager.UpdateCacheSize 的调用,听起来它应该做些什么.不幸的是,在任何正常示例中,s_memoryCacheManager 将始终为空.该字段是从公共静态成员 ObjectCache.Host 设置的.如果用户愿意,这会暴露给用户弄乱,并且我实际上能够通过将我自己的 IMemoryCacheManager 实现放在一起,将其设置为 ObjectCache.Host,然后运行示例来使这件事像它应该的那样工作.不过,到那时,您似乎还不如自己制作缓存实现,甚至不必理会所有这些东西,尤其是因为我不知道是否将您自己的类设置为 ObjectCache.Host (静态,因此它会影响每个其中一些可能正在处理中)来衡量缓存可能会搞砸其他事情.

我必须相信至少有一部分(如果不是几个部分)只是一个直接的错误.很高兴从 MS 的某个人那里听到这件事的经过.

这个巨大答案的 TLDR 版本:假设 CacheMemoryLimitMegabytes 在这个时间点完全被破坏.您可以将其设置为 10 MB,然后继续将缓存填充到 ~2GB 并吹出内存不足异常,而不会触发项目删除.

I’m working with the .NET 4.0 MemoryCache class in an application and trying to limit the maximum cache size, but in my tests it does not appear that the cache is actually obeying the limits.

I'm using the settings which, according to MSDN, are supposed to limit the cache size:

  1. CacheMemoryLimitMegabytes: The maximum memory size, in megabytes, that an instance of an object can grow to."
  2. PhysicalMemoryLimitPercentage: "The percentage of physical memory that the cache can use, expressed as an integer value from 1 to 100. The default is zero, which indicates that MemoryCache instances manage their own memory1 based on the amount of memory that is installed on the computer." 1. This is not entirely correct-- any value below 4 is ignored and replaced with 4.

I understand that these values are approximate and not hard limits as the thread that purges the cache is fired every x seconds and is also dependent on the polling interval and other undocumented variables. However even taking into account these variances, I'm seeing wildly inconsistent cache sizes when the first item is being evicted from the cache after setting CacheMemoryLimitMegabytes and PhysicalMemoryLimitPercentage together or singularly in a test app. To be sure I ran each test 10 times and calculated the average figure.

These are the results of testing the example code below on a 32-bit Windows 7 PC with 3GB of RAM. Size of the cache is taken after the first call to CacheItemRemoved() on each test. (I am aware the actual size of cache will be larger than this)

MemLimitMB    MemLimitPct     AVG Cache MB on first expiry    
   1            NA              84
   2            NA              84
   3            NA              84
   6            NA              84
  NA             1              84
  NA             4              84
  NA            10              84
  10            20              81
  10            30              81
  10            39              82
  10            40              79
  10            49              146
  10            50              152
  10            60              212
  10            70              332
  10            80              429
  10           100              535
 100            39              81
 500            39              79
 900            39              83
1900            39              84
 900            41              81
 900            46              84

 900            49              1.8 GB approx. in task manager no mem errros
 200            49              156
 100            49              153
2000            60              214
   5            60              78
   6            60              76
   7           100              82
  10           100              541

Here is the test application:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
namespace FinalCacheTest
{       
    internal class Cache
    {
        private Object Statlock = new object();
        private int ItemCount;
        private long size;
        private MemoryCache MemCache;
        private CacheItemPolicy CIPOL = new CacheItemPolicy();

        public Cache(long CacheSize)
        {
            CIPOL.RemovedCallback = new CacheEntryRemovedCallback(CacheItemRemoved);
            NameValueCollection CacheSettings = new NameValueCollection(3);
            CacheSettings.Add("CacheMemoryLimitMegabytes", Convert.ToString(CacheSize)); 
            CacheSettings.Add("physicalMemoryLimitPercentage", Convert.ToString(49));  //set % here
            CacheSettings.Add("pollingInterval", Convert.ToString("00:00:10"));
            MemCache = new MemoryCache("TestCache", CacheSettings);
        }

        public void AddItem(string Name, string Value)
        {
            CacheItem CI = new CacheItem(Name, Value);
            MemCache.Add(CI, CIPOL);

            lock (Statlock)
            {
                ItemCount++;
                size = size + (Name.Length + Value.Length * 2);
            }

        }

        public void CacheItemRemoved(CacheEntryRemovedArguments Args)
        {
            Console.WriteLine("Cache contains {0} items. Size is {1} bytes", ItemCount, size);

            lock (Statlock)
            {
                ItemCount--;
                size = size - 108;
            }

            Console.ReadKey();
        }
    }
}

namespace FinalCacheTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            int MaxAdds = 5000000;
            Cache MyCache = new Cache(1); // set CacheMemoryLimitMegabytes

            for (int i = 0; i < MaxAdds; i++)
            {
                MyCache.AddItem(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
            }

            Console.WriteLine("Finished Adding Items to Cache");
        }
    }
}

Why is MemoryCache not obeying the configured memory limits?

解决方案

Wow, so I just spent entirely too much time digging around in the CLR with reflector, but I think I finally have a good handle on what's going on here.

The settings are being read in correctly, but there seems to be a deep-seated problem in the CLR itself that looks like it will render the memory limit setting essentially useless.

The following code is reflected out of the System.Runtime.Caching DLL, for the CacheMemoryMonitor class (there is a similar class that monitors physical memory and deals with the other setting, but this is the more important one):

protected override int GetCurrentPressure()
{
  int num = GC.CollectionCount(2);
  SRef ref2 = this._sizedRef;
  if ((num != this._gen2Count) && (ref2 != null))
  {
    this._gen2Count = num;
    this._idx ^= 1;
    this._cacheSizeSampleTimes[this._idx] = DateTime.UtcNow;
    this._cacheSizeSamples[this._idx] = ref2.ApproximateSize;
    IMemoryCacheManager manager = s_memoryCacheManager;
    if (manager != null)
    {
      manager.UpdateCacheSize(this._cacheSizeSamples[this._idx], this._memoryCache);
    }
  }
  if (this._memoryLimit <= 0L)
  {
    return 0;
  }
  long num2 = this._cacheSizeSamples[this._idx];
  if (num2 > this._memoryLimit)
  {
    num2 = this._memoryLimit;
  }
  return (int) ((num2 * 100L) / this._memoryLimit);
}

The first thing you might notice is that it doesn't even try to look at the size of the cache until after a Gen2 garbage collection, instead just falling back on the existing stored size value in cacheSizeSamples. So you won't ever be able to hit the target right on, but if the rest worked we would at least get a size measurement before we got in real trouble.

So assuming a Gen2 GC has occurred, we run into problem 2, which is that ref2.ApproximateSize does a horrible job of actually approximating the size of the cache. Slogging through CLR junk I found that this is a System.SizedReference, and this is what it's doing to get the value (IntPtr is a handle to the MemoryCache object itself):

[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern long GetApproximateSizeOfSizedRef(IntPtr h);

I'm assuming that extern declaration means that it goes diving into unmanaged windows land at this point, and I have no idea how to start finding out what it does there. From what I've observed though it does a horrible job of trying to approximate the size of the overall thing.

The third noticeable thing there is the call to manager.UpdateCacheSize which sounds like it should do something. Unfortunately in any normal sample of how this should work s_memoryCacheManager will always be null. The field is set from the public static member ObjectCache.Host. This is exposed for the user to mess with if he so chooses, and I was actually able to make this thing sort of work like it's supposed to by slopping together my own IMemoryCacheManager implementation, setting it to ObjectCache.Host, and then running the sample. At that point though, it seems like you might as well just make your own cache implementation and not even bother with all this stuff, especially since I have no idea if setting your own class to ObjectCache.Host (static, so it affects every one of these that might be out there in process) to measure the cache could mess up other things.

I have to believe that at least part of this (if not a couple parts) is just a straight up bug. It'd be nice to hear from someone at MS what the deal was with this thing.

TLDR version of this giant answer: Assume that CacheMemoryLimitMegabytes is completely busted at this point in time. You can set it to 10 MB, and then proceed to fill up the cache to ~2GB and blow an out of memory exception with no tripping of item removal.

这篇关于MemoryCache 不遵守配置中的内存限制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆