9

.NET MemoryCache is a cache of C# objects. Some objects can have a complex structure, and other can have unsafe references. Is C# doing some magic for implementing the PhysicalMemoryLimit or is it just computing the shallow size of each object?

I suspect the later is the case. Still, if I put the same object multiple times in the cache (for tracking missing items, for instance), will the size be accounted a single time, or for each entry that contains that instance?

3
  • I'm pretty sure the cache makes to attempt to compute the size of objects in the cache. It would be futile to do so, since (a) the size of an arbitrary object can change after it has been inserted in the cache, and (b) there may be other references to objects that are referenced by fields of the cached object, so that removing from the cache does not necessarily free all its memory. Commented Jul 25, 2016 at 12:01
  • 1
    Whatever it does, it's implemented by this method: referencesource.microsoft.com/#mscorlib/system/… Commented Jul 26, 2016 at 2:29
  • Changed the content of the question. Commented Jul 29, 2016 at 13:35

4 Answers 4

7
+25

The .NET MemoryCache is similar to the ASP.NET Cache class. If we look at the ASP.NET Cache we see a function called CacheItemRemovedCallback. This is triggerd when a Item is removed from the Cache.

This function gives a CacheItemRemovedReason with the callback function. If we look at the reasons, we see that a item can be removed from the cache because the system removed it to free memory. So while the PhysicalMemoryLimit gives the percentage of physical memory that the cache can use in a single tread, I think they leave it over to the system to clear the cache if it reach the limit.

If you really put a Cache item into the cache with the Add function it will add it as an new CacheItem instance. So it will be accounted multiple times. If you use the function AddOrGetExisting it will check if the item is allready in the cache. If so it will use that instance and not a new instance. So then it will be accounted once.

Hope this helps you in the right direction.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm not sure if this is the case. The measurement is performed on the entire MemoryCache: referencesource.microsoft.com/#System.Runtime.Caching/System/…. So it can well count duplicated object only once.
You are also not considering scenarios where AddOrGetExisting is used with different keys but the same value instance. Is that value counted once or multiple times?
2

Reading the documentation, it appears the cache makes no attempt to compute the size of the objects it is caching. This makes sense because it is not something that can be done from within a process itself for arbitrary types (you can do it for fixed size structs, or arrays of fixed size structs, but that is about it); a bit of googling will confirm that to you. It does however know how much RAM is available on the computer; you can get this yourself from new Microsoft.VisualBasic.Devices.ComputerInfo().AvailablePhysicalMemory. So presumably the cache does two things:

  1. It tracks when each object was last used.
  2. Polls for memory statistics at some interval.

Then on each poll either the amount of available memory is within acceptable limits, or it is not. If it is within acceptable limits it does nothing. If it is not it starts removing items, with the item that was last accessed longest ago removed first. It keeps removing items until the memory gets back within acceptable limits.

If you think about it that is pretty much all you can do with the information available to the cache.

This strategy is OK, but it obviously breaks down if you have other objects holding references to items in the cache, because removing the item from the cache will not free it up for garbage collection. That is the point of the callback, to perform the clean-up to ensure there are no more references to the object.

1 Comment

If you look through the source code you will see that it does in fact get the size of the objects in its cache. It uses a special pointer that instructs the garbage collector to estimate object graph size during its collection cycle. The documentation for MemoryCache is severely lacking. For example, this memory counting method implies that the cache items should be fairly self-contained without references to "parent" objects and such otherwise the object graph size is completely useless.
1

Here's the source. The answer to your second question is obvious if you look at the implementation on Add method: referencesource.microsoft.com that is calling AddOrGetExisting.

I don't know about the size, but I guess that you're right in your assumption that there is no magic at all. Also if you're interested you may inspect the sources in-depth.

5 Comments

That source doesn't answer anything. The user can put the same object multiple types in the cache, under different keys. The MemoryCache doesn't check if the value is already added. It checks only if the key is already added, and for caching purposes the value is usually what takes more space. Plus, that code is not actually counting the size and making any decision to evict items to respect memory limits.
That's not the point (at least as I understand it). For cache purposes the object itself doesn't matter. Only the key matters. And it's reasonable, because there are the same objects that could be used throughout the system multiple times and they need to be stored separately, because the key is what describing the object's purpose in this mechanism.
Also there are calls to unmanaged code to get current memory usage: if (UnsafeNativeMethods.GlobalMemoryStatusEx(ref memoryStatusEx) != 0) { s_totalPhysical = memoryStatusEx.ullTotalPhys; s_totalVirtual = memoryStatusEx.ullTotalVirtual; } which is later utilized while calculating pressure if (memory >= 0x100000000) { _pressureHigh = 99; } else if (memory >= 0x80000000) { _pressureHigh = 98; } Maybe I missed your point, but there are plenty of answers in the sources.
So it is probably getting the cache size directly from OS.
Oh there's magic going on all right...look through the code for SRef. It's a special "sized reference" that makes the GC report the size of the object graph it points to.
-2

You can not easily set the PhysicalMemoryLimit or the CacheMemoryLimit members of the MemoryCache class as they do not have setters implemented (at least in the .Net 4.0 version).

I agree with the other answers in that AddOrGetExisting should be used if you want only a single instance of an object cached. If not you could cache the alternate items with a different key.

2 Comments

The .NET 4 docs seem to suggest otherwise - msdn.microsoft.com/en-us/library/…. Specifically, CacheMemoryLimitMegabytes.
Appart from configuration, you can also set them in the constructor via a NamedValueCollection (key value pairs).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.