MemoryCache: CacheMemoryLimit/PhysicalMemoryLimit funktioniert nicht?

  • C#
  • .NET (FX) 4.0

Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von HD7770.

    MemoryCache: CacheMemoryLimit/PhysicalMemoryLimit funktioniert nicht?

    Guten Abend,
    ich möchte den MemoryCache nutzen um Ergebnisse zu cachen. Allerdings scheint sich der Cache nicht an das Limit zu halten.
    Wenn ich das CacheMemoryLimit z.B. auf 492 MB festlege und das PhysicalMemoryLimit auf 3% setze (bei 16 GB sind das ca. 492 MB), werden trotzdem mehr als 2 GB gespeichert und auch wenn der PollingInterval abläuft wird der Speicherverbrauch nicht gesenkt. Es folgt auch keine OutOfMemoryException. Der Verbrauch sinkt nur wenn z.B. SlidingExpiration gesetzt ist.

    C#-Quellcode

    1. ​ MemoryCache cache = new MemoryCache("Cache", new NameValueCollection
    2. {
    3. {"cacheMemoryLimitMegabytes", "492"},
    4. {"physicalMemoryLimitPercentage", "3"},
    5. {"pollingInterval", "00:00:05" }
    6. });
    7. CacheItemPolicy policy = new CacheItemPolicy
    8. {
    9. Priority = CacheItemPriority.Default
    10. };
    11. try
    12. {
    13. for (int i = 0; i < Int32.MaxValue; i++)
    14. {
    15. cache.Add(i.ToString(), i, policy);
    16. }
    17. }
    18. catch (Exception exc)
    19. {
    20. Console.WriteLine(exc.ToString());
    21. }
    Nach einem kleinen Test, ist es so wie ich gedacht habe.
    Der MemoryCache löscht zwar die Elemente wie angegeben, jedoch bekommt der GC zu wenig Zeit um die gelöschten Elemente aufzuräumen, weswegen der RAM-Verbrauch stetig ansteigt.

    Daher habe ich das mal so Implementiert:

    C#-Quellcode

    1. MemoryCache cache = new MemoryCache("Cache", new NameValueCollection
    2. {
    3. {"cacheMemoryLimitMegabytes", "492"},
    4. {"physicalMemoryLimitPercentage", "3"},
    5. {"pollingInterval", "00:00:05" }
    6. });
    7. CacheItemPolicy policy = new CacheItemPolicy
    8. {
    9. Priority = CacheItemPriority.Default
    10. };
    11. Console.Write("Items in cache:");
    12. Console.CursorVisible = false;
    13. try
    14. {
    15. for (int i = 0; i < Int32.MaxValue; i++)
    16. {
    17. cache.Add(i.ToString(), i, policy);
    18. Console.SetCursorPosition(15, 0);
    19. Console.Write(cache.GetCount());
    20. if (i % 10000 == 0)
    21. {
    22. GC.Collect();
    23. }
    24. }
    25. }
    26. catch (Exception exc)
    27. {
    28. Console.WriteLine(exc.ToString());
    29. }

    Und die Anwendung ist alle 5 Sekunden zu 40MB RAM-Vebrauch zurückgelehrt, was zwar keinem der Limits entspricht, aber zumindest den RAM nicht flutet.

    Ich hoffe doch dass dir bewusst ist, dass der MemoryCache einfach Elemente löscht, wenn er sein Limit erreicht hat?
    msdn.microsoft.com/de-de/libra…percentage(v=vs.110).aspx
    msdn.microsoft.com/de-de/libra…tmegabytes(v=vs.110).aspx

    Edit:
    @HD7770 meine Annahme bezüglich des GCs war etwas verfehlt. Der GC versucht die ganze Zeit, geradezu panisch, die Elemente wegzuräumen, darf es wohl aber nicht. vmtl. weil sich alle Elemente noch in Scope befinden, und damit Theoretisch ansprechbar sind? Leider habe ich nicht mehr als einen Workaround:

    C#-Quellcode

    1. for (int i = 0; i < Int32.MaxValue; i++)
    2. {
    3. cache.Add(i.ToString(), i, policy);
    4. if (i % 50000 == 0)
    5. {
    6. GC.Collect();
    7. }
    8. }
    Das jedenfalls sollte deine Anwendung in ihrem Speicherverbrauch limitieren. Warum der GC so aufräumen kann, und anders nicht? Keine Ahnung. Interessanterweise veringert sich der maximale Speicherverbrauch mit der verringerung der Zahl des Modulos.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „EaranMaleasi“ ()

    In welchem Format liegen die Ergebnisse denn vor? Bzw. wenn du die auch in einem Struct unter bringen kannst, dann kannst du auch einfach ein Array von diesem Anlegen und wie ein RingBuffer verwenden. Da hast du dann kein Stress mit dem GC und kannst die entsprechende Größe auch vorgeben.
    @EaranMaleasi Ja ich weiß das der MemoryCache Elemente löscht wenn er sein Limit erreicht. Er sollte nur nebenbei "mitcachen" um Ladevorgänge zu verkürzen. Das der GC versucht panisch Platz zu machen habe ich auch bemerkt, warum das allerdings nur mit dem Workaround ordentlich funktioniert finde ich verwunderlich.

    @Bluespide Danke für die Idee mit dem RingBuffer. Habe ihn schon implementiert und es funktioniert perfekt.