Ich arbeite mit der .NET 4.0 MemoryCache- Klasse in einer Anwendung und versuche, die maximale Cache-Größe zu begrenzen. In meinen Tests scheint es jedoch nicht so zu sein, dass der Cache tatsächlich die Grenzwerte einhält.
Ich verwende die Einstellungen, die laut MSDN die Cache-Größe begrenzen sollen:
- CacheMemoryLimitMegabytes : Die maximale Speichergröße in Megabyte, auf die eine Instanz eines Objekts anwachsen kann. "
- PhysicalMemoryLimitPercentage : "Der Prozentsatz des physischen Speichers, den der Cache verwenden kann, ausgedrückt als ganzzahliger Wert von 1 bis 100. Der Standardwert ist Null. Dies gibt an, dass MemoryCache- Instanzen ihren eigenen Speicher 1 basierend auf derauf dem Computer installierten Speichermenge verwalten Computer." 1. Dies ist nicht ganz richtig - jeder Wert unter 4 wird ignoriert und durch 4 ersetzt.
Ich verstehe, dass diese Werte ungefähre und keine harten Grenzen sind, da der Thread, der den Cache löscht, alle x Sekunden ausgelöst wird und auch vom Abfrageintervall und anderen undokumentierten Variablen abhängt. Selbst unter Berücksichtigung dieser Abweichungen sehe ich wild inkonsistente Cache-Größen, wenn das erste Element aus dem Cache entfernt wird, nachdem CacheMemoryLimitMegabytes und PhysicalMemoryLimitPercentage zusammen oder einzeln in einer Test-App festgelegt wurden. Um sicherzugehen, habe ich jeden Test 10 Mal durchgeführt und die durchschnittliche Zahl berechnet.
Dies sind die Ergebnisse des Testens des folgenden Beispielcodes auf einem 32-Bit-Windows 7-PC mit 3 GB RAM. Die Größe des Caches wird nach dem ersten Aufruf von CacheItemRemoved () bei jedem Test übernommen. (Mir ist bekannt, dass die tatsächliche Größe des Caches größer sein wird)
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
Hier ist die Testanwendung:
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");
}
}
}
Warum hält MemoryCache die konfigurierten Speichergrenzen nicht ein?
quelle
Antworten:
Wow, also habe ich einfach zu viel Zeit damit verbracht, mit Reflektor in der CLR herumzuwühlen, aber ich denke, ich habe endlich einen guten Überblick darüber, was hier vor sich geht.
Die Einstellungen werden korrekt eingelesen, aber es scheint ein tiefgreifendes Problem in der CLR selbst zu geben, das die Einstellung des Speicherlimits im Wesentlichen unbrauchbar macht.
Der folgende Code wird in der System.Runtime.Caching-DLL für die CacheMemoryMonitor-Klasse wiedergegeben (es gibt eine ähnliche Klasse, die den physischen Speicher überwacht und sich mit der anderen Einstellung befasst, dies ist jedoch die wichtigere):
Das erste, was Sie möglicherweise bemerken, ist, dass erst nach einer Gen2-Garbage Collection versucht wird, die Größe des Caches zu überprüfen, sondern nur auf den vorhandenen Wert für die gespeicherte Größe in cacheSizeSamples zurückzugreifen. Sie werden also nie in der Lage sein, das Ziel direkt zu treffen, aber wenn der Rest funktioniert, würden wir zumindest eine Größenmessung erhalten, bevor wir in echte Schwierigkeiten geraten.
Unter der Annahme, dass ein Gen2-GC aufgetreten ist, stoßen wir auf Problem 2, nämlich dass ref2.ApproximateSize einen schrecklichen Job darin macht, die Größe des Caches tatsächlich zu approximieren. Beim Durchsuchen von CLR-Junk stellte ich fest, dass es sich um eine System.SizedReference handelt, und dies geschieht, um den Wert abzurufen (IntPtr ist ein Handle für das MemoryCache-Objekt selbst):
Ich gehe davon aus, dass eine externe Deklaration bedeutet, dass sie zu diesem Zeitpunkt in nicht verwaltete Fenster landet, und ich habe keine Ahnung, wie ich herausfinden soll, was sie dort tut. Nach allem, was ich beobachtet habe, macht es einen schrecklichen Job, die Größe der gesamten Sache zu approximieren.
Das dritte bemerkenswerte Problem ist der Aufruf von manager.UpdateCacheSize, der so klingt, als ob er etwas tun sollte. Leider ist s_memoryCacheManager in jedem normalen Beispiel, wie dies funktionieren sollte, immer null. Das Feld wird vom öffentlichen statischen Member ObjectCache.Host festgelegt. Dies ist für den Benutzer offen, mit dem er sich herumschlagen kann, wenn er dies wünscht, und ich konnte diese Sache tatsächlich so machen, wie sie sollte, indem ich meine eigene IMemoryCacheManager-Implementierung zusammenfügte, sie auf ObjectCache.Host setzte und dann das Beispiel ausführte . An diesem Punkt scheint es jedoch so, als könnten Sie genauso gut Ihre eigene Cache-Implementierung erstellen und sich nicht einmal mit all diesen Dingen beschäftigen, zumal ich keine Ahnung habe, ob Sie Ihre eigene Klasse auf ObjectCache.Host setzen (statisch,
Ich muss glauben, dass zumindest ein Teil davon (wenn nicht ein paar Teile) nur ein direkter Fehler ist. Es wäre schön, von jemandem bei MS zu hören, was mit dieser Sache los war.
TLDR-Version dieser riesigen Antwort: Angenommen, CacheMemoryLimitMegabytes ist zu diesem Zeitpunkt vollständig kaputt. Sie können es auf 10 MB einstellen und dann den Cache auf ~ 2 GB füllen und eine Ausnahme wegen Speichermangels auslösen, ohne dass das Entfernen von Elementen ausgelöst wird.
quelle
Ich weiß, dass diese Antwort spät verrückt ist, aber besser spät als nie. Ich wollte Sie wissen lassen, dass ich eine Version geschrieben habe
MemoryCache
, die die Probleme mit der Gen 2-Sammlung automatisch für Sie löst. Es wird daher immer dann gekürzt, wenn das Abfrageintervall den Speicherdruck anzeigt. Wenn dieses Problem auftritt, probieren Sie es aus!http://www.nuget.org/packages/SharpMemoryCache
Sie können es auch auf GitHub finden, wenn Sie neugierig sind, wie ich es gelöst habe. Der Code ist etwas einfach.
https://github.com/haneytron/sharpmemorycache
quelle
2n + 20
in Bezug auf Bytes ungefähr so groß, wobein
die Länge der Zeichenfolge angegeben ist. Dies ist hauptsächlich auf die Unicode-Unterstützung zurückzuführen.Ich bin auch auf dieses Problem gestoßen. Ich speichere Objekte zwischen, die Dutzende Male pro Sekunde in meinen Prozess abgefeuert werden.
Ich habe festgestellt, dass die folgende Konfiguration und Verwendung die Elemente die meiste Zeit alle 5 Sekunden freigibt .
App.config:
Beachten Sie cacheMemoryLimitMegabytes . Wenn dies auf Null gesetzt wurde, wurde die Spülroutine nicht in einer angemessenen Zeit ausgelöst.
Zum Cache hinzufügen:
Bestätigen, dass das Entfernen des Caches funktioniert:
quelle
Ich bin gestern (zum Glück) auf diesen nützlichen Beitrag gestoßen, als ich zum ersten Mal versuchte, den MemoryCache zu verwenden. Ich dachte, es wäre ein einfacher Fall, Werte festzulegen und die Klassen zu verwenden, aber ich stieß auf ähnliche Probleme, die oben beschrieben wurden. Um zu sehen, was los war, extrahierte ich die Quelle mit ILSpy, richtete einen Test ein und ging den Code durch. Mein Testcode war dem obigen Code sehr ähnlich, daher werde ich ihn nicht veröffentlichen. Bei meinen Tests habe ich festgestellt, dass die Messung der Cache-Größe nie besonders genau war (wie oben erwähnt) und angesichts der aktuellen Implementierung niemals zuverlässig funktionieren würde. Die physikalische Messung war jedoch in Ordnung, und wenn das physikalische Gedächtnis bei jeder Umfrage gemessen wurde, schien es mir, als würde der Code zuverlässig funktionieren. Also habe ich die Garbage Collection-Prüfung der 2. Generation in MemoryCacheStatistics entfernt.
In einem Testszenario macht dies offensichtlich einen großen Unterschied, da der Cache ständig getroffen wird, sodass Objekte nie die Chance haben, zu Gen 2 zu gelangen. Ich denke, wir werden den modifizierten Build dieser DLL für unser Projekt verwenden und die offizielle MS verwenden Build, wenn .net 4.5 herauskommt (das laut dem oben erwähnten Connect-Artikel das Update enthalten sollte). Logischerweise kann ich sehen, warum der Gen 2-Check durchgeführt wurde, aber in der Praxis bin ich mir nicht sicher, ob er sinnvoll ist. Wenn der Speicher 90% erreicht (oder welche Grenze auch immer festgelegt wurde), sollte es keine Rolle spielen, ob eine Gen 2-Sammlung stattgefunden hat oder nicht. Elemente sollten trotzdem entfernt werden.
Ich habe meinen Testcode etwa 15 Minuten lang laufen lassen, wobei der Wert für PhysicalMemoryLimitPercentage auf 65% festgelegt war. Ich habe gesehen, dass die Speichernutzung während des Tests zwischen 65 und 68% liegt, und habe gesehen, dass die Dinge ordnungsgemäß entfernt wurden. In meinem Test habe ich das pollingInterval auf 5 Sekunden festgelegt, physischMemoryLimitPercentage auf 65 und physischMemoryLimitPercentage auf 0, um dies als Standard festzulegen.
Befolgen Sie die obigen Ratschläge; Eine Implementierung von IMemoryCacheManager könnte vorgenommen werden, um Dinge aus dem Cache zu entfernen. Es würde jedoch unter dem erwähnten Gen 2 Check-Problem leiden. Abhängig vom Szenario ist dies möglicherweise kein Problem im Produktionscode und funktioniert möglicherweise ausreichend für Personen.
quelle
Ich habe einige Tests am Beispiel von @Canacourse und der Modifikation von @woany durchgeführt und denke, dass es einige kritische Aufrufe gibt, die die Bereinigung des Speichercaches blockieren.
Aber warum scheint die Modifikation von @woany den Speicher auf dem gleichen Niveau zu halten? Erstens ist der RemovedCallback nicht gesetzt und es gibt keine Konsolenausgabe oder Warten auf Eingaben, die den Thread des Speichercaches blockieren könnten.
Zweitens...
Ein Thread.Sleep (1) jedes ~ 1000. AddItem () hätte den gleichen Effekt.
Nun, es ist keine sehr gründliche Untersuchung des Problems, aber es sieht so aus, als ob der Thread des MemoryCache nicht genügend CPU-Zeit zum Bereinigen erhält, während viele neue Elemente hinzugefügt werden.
quelle
Es stellte sich heraus, dass es kein Fehler ist. Alles, was Sie tun müssen, ist die Pooling-Zeitspanne festzulegen, um die Grenzwerte durchzusetzen. Wenn Sie das Pooling nicht festgelegt lassen, wird es anscheinend nie ausgelöst. Ich habe es nur getestet und brauche keine Wrapper oder ein zusätzlicher Code:
Stellen Sie den Wert von "
PollingInterval
" basierend darauf ein, wie schnell der Cache wächst. Wenn er zu schnell wächst, erhöhen Sie die Häufigkeit der Abfrageprüfungen. Andernfalls bleiben die Überprüfungen nicht sehr häufig, um keinen Overhead zu verursachen.quelle
Wenn Sie die folgende geänderte Klasse verwenden und den Speicher über den Task-Manager überwachen, wird er tatsächlich gekürzt:
quelle
MemoryCache
. Ich frage mich, warum dieses Beispiel funktioniert.