wiki:MemoryCache

Overview

MemoryCache module is a part of the bigger mechanism responsible for page loading called loader. You can learn more about loader from this article (https://www.webkit.org/blog/427/webkit-page-cache-i-the-basics/). This article focuses only on Memory Cache aspects and its internal components without interaction with loader.

Simple description what MemoryCache is and its purpose can be found in MemoryCache.h header:

    This cache holds subresources used by Web pages: images, scripts, stylesheets, etc. The cache keeps a flexible but bounded window of dead resources that grows/shrinks depending on the live resource load. Here's an example of cache growth over time, with a min dead resource capacity of 25% and a max dead resource capacity of 50%:

            |-----|                   Dead: -
            |----------|              Live: +
          --|----------|              Cache boundary: | (objects outside this mark have been evicted)
          --|----------++++++++++|
     -------|-----+++++++++++++++|
     -------|-----+++++++++++++++|+++++

     The behavior of the cache changes in the following way if shouldMakeResourcePurgeableOnEviction returns true.
     1. Dead resources in the cache are kept in non-purgeable memory.
     2. When we prune dead resources, instead of freeing them, we mark their memory as purgeable and keep the resources until the kernel reclaims the purgeable memory.

     By leaving the in-cache dead resources in dirty resident memory, we decrease the likelihood of the kernel claiming that memory and forcing us to refetch the resource (for example when a user presses back).

     And by having an unbounded number of resource objects using purgeable memory, we can use as much memory as it is available on the machine. The trade-off here is that the CachedResource object (and its member variables) are allocated in non-purgeable TC-malloc'd memory so we would see slightly more memory use due to this.

What exactly means that some memory is purgeable/non-purgeable? Well, some operating systems allows to mark memory as purgeable. It means that since it is marked the content may disappear (the system expropriatse it) at any point of time (until it gets unmarked) without informing the application. So the application may know if the memory is purged after it asks the system for the resource (this may be implemented differently on separate systems). This leads to serious consequences in object life cycle management:

  • this kind of memory disallows to store objects that depends on its (or related by inheritance/consistency) destructor
  • ownership/references to the memory should never be passed to the modules that wouldn't treat this memory as a “special object”.
  • known and widely used techniques of managing object lifetime (like smart pointers) are not relevant to objects stored in purgable memory

Now, after you know about consequences of storing objects in purgeable memory let's take a look on how the cache module is constructed.

MemoryCache class

This class is designed to store and manage objects (web resources) lifecycle. Stored resources may be live or dead. A resource is dead when there are no references from web pages. And vice versa resource is live when there is at least one reference to it from web page.

MemoryCache client may have influence on RAM usage using three values:

  • min dead bytes - minimum size of dead resources when purging
  • max dead bytes - maximum size of dead resources when purging
  • total capacity bytes - sum of live and dead resources capacity sizes

Don't panic. Total capacity does not determine upper boundary of resources size that WebKit would keep in memory therefore will not be a situation when only half of your favourite page will be loaded. It is only a threshold to decide when to prune resources. But what exactly means to prune? First, let's explain the meaning of live and dead resources. When a resource is downloaded it is stored in memory as raw data (it can be an image, CSS, JavaScript etc.). Such data needs to be decoded (parsed) to make it usable so when the resource is decoded (it means implicitly that some CachedResourceClient needs it) it starts to be a live resource. It will be dead after last CachedResourceClient stops using it. Anyway live or dead resources may contain decoded data that (depending on external factors) unnecessarily occupies memory. Therefore prune() method is provided. In first step MemoryCache will prune dead resources (to meet minimum and maximum dead resources size boundary) and then live resources one by one until all resources size exceeds total capacity.

Prune procedure provides one more mechanism to reduce memory consumption. In the beginning of this article I've mentioned about quaint technique - purgeable memory. CachedResource has ability to move stored raw data to special buffer called PurgeableBuffer. Since data is located in such buffer the operating system may take over (purge) memory allocated for it. So if a resource buffer was purged the prune procedure would also remove all CachedResource data related to the buffer - it's called eviction. Evict will also remove a resource from MemoryCache internal structures and after that the only way to restore lost data is to regain it basing on related ResourceRequest.

LRU lists

MemoryCache keeps references to resources in three different structures:

  • m_resources - a LRU-based map of all resources kept in cache
  • m_allResources - Vector of LRU lists with fixed size (32). Basing on CachedResource access counter a resource is located in appropriate list. Prune procedure will start evict beginning from last recently used resources.
  • m_liveDecodedResources - live resources with decoded data. If stored resources significantly exceeds total capacity MemoryCache tries to remove decoded data one by one until boundary assumptions will be fulfilled or there would be nothing redundant data to release.
Last modified 4 years ago Last modified on Aug 26, 2013 6:27:45 AM

Attachments (1)

Download all attachments as: .zip