Я работаю с классом .NET 4.0 MemoryCache в приложении и пытаюсь ограничить максимальный размер кеша, но в моих тестах не оказалось, что кеш действительно подчиняется ограничениям.
Я использую настройки, которые, согласно MSDN , должны ограничивать размер кеша:
- CacheMemoryLimitMegabytes : максимальный размер памяти в мегабайтах, до которого может увеличиться экземпляр объекта ".
- PhysicalMemoryLimitPercentage : «Процент физической памяти, которую может использовать кэш, выраженный в виде целого числа от 1 до 100. По умолчанию - ноль, что указывает на то, чтоэкземпляры MemoryCache управляют своей собственной памятью 1 в зависимости от объема памяти, установленной на компьютер ". 1. Это не совсем правильно - любое значение ниже 4 игнорируется и заменяется на 4.
Я понимаю, что эти значения являются приблизительными, а не жесткими ограничениями, поскольку поток, очищающий кеш, запускается каждые x секунд и также зависит от интервала опроса и других недокументированных переменных. Однако, даже принимая во внимание эти различия, я наблюдаю совершенно несогласованные размеры кеша, когда первый элемент удаляется из кеша после установки CacheMemoryLimitMegabytes и PhysicalMemoryLimitPercentage вместе или по отдельности в тестовом приложении. Чтобы быть уверенным, я запускал каждый тест 10 раз и вычислял среднее значение.
Это результаты тестирования приведенного ниже примера кода на 32-разрядном ПК с Windows 7 и 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 не подчиняется установленным ограничениям памяти?
источник
Ответы:
Вау, я просто потратил слишком много времени, копаясь в 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 произошел, мы сталкиваемся с проблемой 2, которая заключается в том, что ref2.ApproximateSize выполняет ужасную работу по фактическому приближению размера кеша. Просматривая мусор CLR, я обнаружил, что это System.SizedReference, и это то, что он делает для получения значения (IntPtr - это дескриптор самого объекта MemoryCache):
[SecurityCritical] [MethodImpl(MethodImplOptions.InternalCall)] private static extern long GetApproximateSizeOfSizedRef(IntPtr h);
Я предполагаю, что объявление extern означает, что на этом этапе он переходит в неуправляемую область Windows, и я не знаю, как начать выяснять, что он там делает. Судя по тому, что я наблюдал, он ужасно пытается приблизиться к размеру всей вещи.
Третья примечательная вещь - это вызов manager.UpdateCacheSize, который звучит так, как будто он должен что-то делать. К сожалению, в любом нормальном примере того, как это должно работать, s_memoryCacheManager всегда будет иметь значение null. Поле устанавливается из общедоступного статического члена ObjectCache.Host. Это открыто для пользователя, с которым он может возиться, если он того пожелает, и я действительно смог заставить эту работу работать так, как предполагалось, путем объединения моей собственной реализации IMemoryCacheManager, установки ее на ObjectCache.Host, а затем запуска образца . Однако в этот момент кажется, что вы могли бы просто создать свою собственную реализацию кеша и даже не беспокоиться обо всем этом, тем более что я понятия не имею, устанавливаете ли ваш собственный класс в ObjectCache.Host (static,
Я должен верить, что по крайней мере часть этого (если не пара частей) - просто явная ошибка. Было бы неплохо услышать от кого-нибудь из MS, в чем заключалась сделка с этой штукой.
TL; DR-версия этого гигантского ответа: предположим, что CacheMemoryLimitMegabytes полностью заблокирован в этот момент времени. Вы можете установить его на 10 МБ, а затем приступить к заполнению кеша до ~ 2 ГБ и исключению нехватки памяти без отключения удаления элементов.
источник
Я знаю, что это безумно поздно, но лучше поздно, чем никогда. Я хотел сообщить вам, что я написал версию,
MemoryCache
которая автоматически решает проблемы с Gen 2 Collection для вас. Поэтому он обрезается всякий раз, когда интервал опроса указывает на нехватку памяти. Если у вас возникла эта проблема, попробуйте!http://www.nuget.org/packages/SharpMemoryCache
Вы также можете найти его на GitHub, если вам интересно, как я это решил. Код несколько прост.
https://github.com/haneytron/sharpmemorycache
источник
2n + 20
размеру байтов, гдеn
- длина строки. В основном это связано с поддержкой Unicode.Я провел некоторое тестирование на примере @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, кажется, сохраняет память на одном уровне? Во-первых, RemovedCallback не установлен, и нет вывода консоли или ожидания ввода, который мог бы заблокировать поток кеша памяти.
Во-вторых ...
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 не получает достаточно процессорного времени для очистки, при этом добавляется много новых элементов.
источник
Я тоже столкнулся с этой проблемой. Я кэширую объекты, которые запускаются в мой процесс десятки раз в секунду.
Я обнаружил, что следующая конфигурация и использование освобождают элементы каждые 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()); }
источник
Я (к счастью) наткнулся на этот полезный пост вчера, когда впервые пытался использовать MemoryCache. Я думал, что это будет простой случай установки значений и использования классов, но я столкнулся с аналогичными проблемами, описанными выше. Чтобы попытаться понять, что происходит, я извлек исходный код с помощью ILSpy, а затем настроил тест и прошел через код. Мой тестовый код был очень похож на код выше, поэтому я не буду его публиковать. Из своих тестов я заметил, что измерение размера кеша никогда не было особенно точным (как упоминалось выше) и, учитывая текущую реализацию, никогда не будет работать надежно. Однако физическое измерение было в порядке, и если физическая память измерялась при каждом опросе, мне казалось, что код будет работать надежно. Итак, я удалил проверку сборки мусора второго поколения в MemoryCacheStatistics;
В тестовом сценарии это, очевидно, имеет большое значение, поскольку кеш постоянно поражается, поэтому у объектов никогда не будет возможности добраться до поколения 2. Я думаю, что мы собираемся использовать модифицированную сборку этой dll в нашем проекте и использовать официальный MS построить, когда выходит .net 4.5 (который, согласно упомянутой выше статье о подключении, должен содержать исправление). Логически я могу понять, почему была введена проверка поколения 2, но на практике я не уверен, имеет ли это большой смысл. Если объем памяти достигает 90% (или любого другого установленного предела), то не имеет значения, произошла ли коллекция поколения 2 или нет, элементы должны быть исключены независимо.
Я оставил свой тестовый код запущенным примерно на 15 минут, установив для PhysicalMemoryLimitPercentage значение 65%. Я видел, что использование памяти остается в пределах 65-68% во время теста, и видел, как вещи удаляются должным образом. В моем тесте я установил pollingInterval равным 5 секундам, PhysicalMemoryLimitPercentage равным 65 и PhysicalMemoryLimitPercentage равным 0 по умолчанию.
Следуя приведенному выше совету; реализация IMemoryCacheManager может быть выполнена для удаления вещей из кеша. Однако он будет страдать от упомянутой проблемы проверки второго поколения. Хотя, в зависимости от сценария, это может не быть проблемой в производственном коде и может в достаточной степени работать для людей.
источник
Оказалось, что это не ошибка, все, что вам нужно сделать, это установить временной интервал объединения, чтобы обеспечить соблюдение ограничений, кажется, если вы оставите пул не установленным, он никогда не сработает. Я только что протестировал его, и нет необходимости в обертках или любой дополнительный код:
private static readonly NameValueCollection Collection = new NameValueCollection { {"CacheMemoryLimitMegabytes", "20"}, {"PollingInterval", TimeSpan.FromMilliseconds(60000).ToString()}, // this will check the limits each 60 seconds };
Установите значение "
PollingInterval
" в зависимости от того, насколько быстро растет кэш, если он растет слишком быстро, увеличьте частоту проверок опроса, в противном случае держите проверки не очень частыми, чтобы не вызывать накладных расходов.источник
Если вы используете следующий модифицированный класс и отслеживаете память с помощью диспетчера задач, на самом деле обрезается:
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
. Интересно, почему этот образец работает.