c# MemoryCache لا يطيع حدود الذاكرة في التكوين




.net caching (5)

أنا أعمل مع فئة .NET 4.0 MemoryCache في تطبيق ومحاولة تحديد الحد الأقصى لحجم ذاكرة التخزين المؤقت ، ولكن في الاختبارات التي أجريتها لا يبدو أن ذاكرة التخزين المؤقت تطيع في الواقع حدود.

أنا أستخدم الإعدادات التي ، وفقًا لـ MSDN ، من المفترض أن تحد حجم ذاكرة التخزين المؤقت:

  1. CacheMemoryLimitMegabytes : الحد الأقصى لحجم الذاكرة ، بالميغابايت ، التي يمكن أن CacheMemoryLimitMegabytes مثيل لها إلى. "
  2. PhysicalMemoryLimitPercentage : "النسبة المئوية للذاكرة الفعلية التي يمكن أن تستخدمها ذاكرة التخزين المؤقت ، والتي يتم التعبير عنها كقيمة صحيحة من 1 إلى 100. الافتراضي هو صفر ، والذي يشير إلى أن مثيلات الذاكرة MemoryCache تقوم بإدارة الذاكرة الخاصة بهم 1 بناءً على مقدار الذاكرة المثبتة على الحاسوب." 1. هذا ليس صحيحًا تمامًا - يتم تجاهل أي قيمة أقل من 4 واستبدالها بـ 4.

أتفهم أن هذه القيم تقريبية وليست حدودًا صلبة نظرًا لأن السلاسل التي تطبع ذاكرة التخزين المؤقت تطلق كل ثانية x وتعتمد أيضًا على الفاصل الزمني للاستقصاء والمتغيرات غير الموثقة الأخرى. ومع ذلك ، حتى مع مراعاة هذه التباينات ، أرى أحجام ذاكرة التخزين مؤقت غير متناسقة بشكل كبير عندما يتم طرد العنصر الأول من ذاكرة التخزين المؤقت بعد تعيين CacheMemoryLimitMegabytes و PhysicalMemoryLimitPercentage معًا أو بشكل فردي في تطبيق اختبار. للتأكد من أني أجريت كل اختبار 10 مرات وحسبت متوسط ​​الرقم.

هذه هي نتائج اختبار رمز المثال أدناه على كمبيوتر Windows 7 32-بت مع 3 غيغابايت من ذاكرة الوصول العشوائي. يتم أخذ حجم ذاكرة التخزين المؤقت بعد الاستدعاء الأول إلى CacheItemRemoved () على كل اختبار. (أعلم أن الحجم الفعلي لذاكرة التخزين المؤقت سيكون أكبر من هذا)

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

هنا هو تطبيق الاختبار:

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

لماذا لا يتوافق تطبيق MemoryCache مع حدود الذاكرة التي تمت تهيئتها؟


لقد أجريت بعض الاختبارات مع مثالCanacourse وتعديلwoany وأعتقد أن هناك بعض المكالمات الهامة التي تمنع تنظيف ذاكرة التخزين المؤقت.

public void CacheItemRemoved(CacheEntryRemovedArguments Args)
{
    // this WriteLine() will block the thread of
    // the MemoryCache long enough to slow it down,
    // and it will never catch up the amount of memory
    // beyond the limit
    Console.WriteLine("...");

    // ...

    // this ReadKey() will block the thread of 
    // the MemoryCache completely, till you press any key
    Console.ReadKey();
}

ولكن لماذا يبدو أن تعديلwoany يحافظ على الذاكرة على نفس المستوى؟ أولاً ، لم يتم تعيين RemoveedCallback ولا يوجد إخراج وحدة تحكم أو انتظار الإدخال الذي قد يمنع مؤشر ترابط ذاكرة التخزين المؤقت.

ثانيا...

public void AddItem(string Name, string Value)
{
    // ...

    // this WriteLine will block the main thread long enough,
    // so that the thread of the MemoryCache can do its work more frequently
    Console.WriteLine("...");
}

سيكون Thread.Sleep (1) كل ~ 1000 AddItem () نفس التأثير.

حسنا ، انها ليست تحقيقا عميقا جدا للمشكلة ، ولكن يبدو كما لو أن مؤشر ترابط ذاكرة MemoryCache لا يحصل على ما يكفي من وقت وحدة المعالجة المركزية للتنظيف ، في حين يتم إضافة العديد من العناصر الجديدة.


واو ، لقد قضيت الكثير من الوقت في التنقيب في CLR مع عاكس ، ولكن أعتقد أنني أخيراً أتعامل بشكل جيد مع ما يجري هنا.

تتم قراءة الإعدادات بشكل صحيح ، ولكن يبدو أن هناك مشكلة عميقة في CLR نفسها يبدو أنها ستجعل إعداد الحد الأقصى للذاكرة عديم الفائدة بشكل أساسي.

التعليمة البرمجية التالية تنعكس من DLL System.Runtime.Caching ، لفئة 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 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);
}

أول شيء قد تلاحظ أنه لا يحاول حتى النظر إلى حجم ذاكرة التخزين المؤقت حتى بعد جمع القمامة Gen2 ، بدلاً من مجرد الرجوع إلى قيمة الحجم المخزن الموجودة في cacheSizeSamples. لذلك لن تكون قادرًا على ضرب الهدف على الإطلاق ، ولكن إذا عمل الباقي على الأقل سنحصل على قياس حجم قبل أن نواجه مشكلة حقيقية.

وبافتراض حدوث Gen2 GC ، فإننا نواجه مشكلة 2 ، وهي أن ref2.ApproximateSize تقوم بعمل فظيع تقريبًا لحجم ذاكرة التخزين المؤقت. slogging عبر junk CLR وجدت أن هذا هو System.SizedReference ، وهذا ما يفعله للحصول على القيمة (IntPtr هو مؤشر إلى كائن MemoryCache نفسه):

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

أفترض أن إعلان extern يعني أنه يذهب الغوص في الأرض windows غير مدار في هذه المرحلة ، وليس لدي فكرة عن كيفية البدء في معرفة ما يفعل هناك. من ما لاحظته على الرغم من أنه يقوم بعمل رهيب من محاولة لتقريب حجم الشيء الكلي.

الشيء الثالث الملحوظ هو دعوة المديرين.تحديث "أوبديكاتشيسيزي" الذي يبدو وكأنه يجب أن يفعل شيئًا. لسوء الحظ في أي عينة طبيعية من كيفية عمل هذا s_memoryCacheManager سيكون دائما لاغيا. يتم تعيين الحقل من عضو ثابت عام ObjectCache.Host. هذا يتعرض للمستخدم لفوضى إذا اختار ذلك ، وكنت في الواقع قادرة على جعل هذا النوع من العمل مثل من المفترض أنه من خلال سحق بلدي تطبيق IMemoryCacheManager ، وتعيينه إلى ObjectCache.Host ، ثم تشغيل العينة . في هذه المرحلة ، يبدو أنك قد تقوم بتطبيق ذاكرة التخزين المؤقت الخاصة بك فقط ولا تزعج نفسك بكل هذه الأشياء ، خاصة وأنني لا أعلم إذا كان إعداد صفك الخاص على ObjectCache.Host (ثابت ، لذلك يؤثر على كل شيء من تلك التي قد تكون هناك في العملية) لقياس ذاكرة التخزين المؤقت يمكن أن تفسد أشياء أخرى.

يجب أن أؤمن بأن جزءًا على الأقل من هذا (إن لم يكن جزءًا من الاثنين) هو مجرد خطأ مستقيمي. سيكون من الجميل أن نسمع من شخص ما في MS ما كانت الصفقة مع هذا الشيء.

إصدار TLDR من هذه الإجابة العملاقة: افترض أن CacheMemoryLimitMegabytes تم ضبطه تمامًا في هذه المرحلة الزمنية. يمكنك تعيينه إلى 10 ميغابايت ، ثم المتابعة لملء ذاكرة التخزين المؤقت إلى ~ 2GB وتفجير خارج استثناء الذاكرة دون تعثر إزالة العنصر.


أعرف أن هذه الإجابة مجنونة متأخرة ، لكن متأخرة أكثر من عدمها. أردت أن MemoryCache بأنني كتبت إصدارًا من MemoryCache يعمل على حل مشكلات مجموعة Gen 2 تلقائيًا لك. لذلك فإنه يتم تقليل الوقت كلما يشير الفاصل الزمني للاستقصاء إلى ضغط الذاكرة. إذا كنت تواجه هذه المشكلة ، فابحث عنها!

http://www.nuget.org/packages/SharpMemoryCache

يمكنك أيضًا العثور عليه على GitHub إذا كنت مهتمًا بكيفية حلها. الرمز بسيط إلى حد ما.

https://github.com/haneytron/sharpmemorycache


لقد واجهت هذه القضية كذلك. أقوم بتخزين الكائنات التي يتم تشغيلها في عملي عدة مرات في الثانية.

لقد وجدت التكوين والاستخدام التالي يحرر العناصر كل 5 ثوان معظم الوقت .

App.config:

تأخذ ملاحظة cacheMemoryLimitMegabytes . عندما تم تعيين هذا إلى الصفر ، لن يطلق روتين التطهير في وقت معقول.

   <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="Default" cacheMemoryLimitMegabytes="20" physicalMemoryLimitPercentage="0" pollingInterval="00:00:05" />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>  

إضافة إلى ذاكرة التخزين المؤقت:

MemoryCache.Default.Add(someKeyValue, objectToCache, new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddSeconds(5), RemovedCallback = cacheItemRemoved });

يعمل تأكيد إزالة ذاكرة التخزين المؤقت على ما يلي:

void cacheItemRemoved(CacheEntryRemovedArguments arguments)
{
    System.Diagnostics.Debug.WriteLine("Item removed from cache: {0} at {1}", arguments.CacheItem.Key, DateTime.Now.ToString());
}

إذا كنت تستخدم الفئة المعدلة التالية ومراقبة الذاكرة عن طريق "إدارة المهام" في الواقع الحصول على قلص:

internal class Cache
{
    private Object Statlock = new object();
    private int ItemCount;
    private long size;
    private MemoryCache MemCache;
    private CacheItemPolicy CIPOL = new CacheItemPolicy();

    public Cache(double CacheSize)
    {
        NameValueCollection CacheSettings = new NameValueCollection(3);
        CacheSettings.Add("cacheMemoryLimitMegabytes", Convert.ToString(CacheSize));
        CacheSettings.Add("pollingInterval", Convert.ToString("00:00:01"));
        MemCache = new MemoryCache("TestCache", CacheSettings);
    }

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

        Console.WriteLine(MemCache.GetCount());
    }
}




memorycache