{"id":1704,"date":"2012-02-08T12:22:40","date_gmt":"2012-02-08T01:22:40","guid":{"rendered":"http:\/\/blog.mozilla.org\/nnethercote\/?p=1704"},"modified":"2012-02-08T13:44:19","modified_gmt":"2012-02-08T02:44:19","slug":"the-benefits-of-reducing-memory-consumption-2","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/nnethercote\/2012\/02\/08\/the-benefits-of-reducing-memory-consumption-2\/","title":{"rendered":"The benefits of reducing memory consumption"},"content":{"rendered":"<p><strong>TL;DR: Any single change that reduces Firefox&#8217;s memory consumption can affect Firefox&#8217;s speed, stability and reputation in a variety of ways, some of which are non-obvious.\u00a0 Some examples illustrate this.<br \/>\n<\/strong><\/p>\n<p>The <a href=\"https:\/\/wiki.mozilla.org\/Performance\/MemShrink\">MemShrink wiki page<\/a> starts with the following text.<\/p>\n<blockquote><p>MemShrink is a project that aims to reduce Firefox&#8217;s memory consumption. There are three potential benefits.\u00a0 Speed. [&#8230;] Stability. [&#8230;] Reputation.&#8221;<\/p><\/blockquote>\n<p>I want to dig more deeply into these benefits and the question of what it means to &#8220;reduce Firefox&#8217;s memory consumption&#8221;, because there are some subtleties involved.\u00a0 In what follows I will use the term &#8220;MemShrink optimization&#8221; to refer to any change that reduces Firefox&#8217;s memory consumption.<\/p>\n<h3>Speed<\/h3>\n<p>People tend to associate low memory consumption with speed.\u00a0 However, time\/space trade-offs abound in programming, and an explicit goal of MemShrink is to not slow Firefox down &#8212; the wiki page says:<\/p>\n<blockquote><p>Changes that reduce memory consumption but make Firefox slower are not desirable.<\/p><\/blockquote>\n<p>There are several ways that MemShrink optimizations can improve performance.<\/p>\n<h4>Paging<\/h4>\n<p>The case that people probably think of first is paging.\u00a0 If physical memory fills up and the machine needs to start paging, i.e. evicting virtual memory pages to disk space, it can be catastrophic for performance.\u00a0 This is because disk accesses are many thousands of times slower than RAM accesses.<\/p>\n<p>However, some MemShrink optimizations are far more likely to affect paging than others.\u00a0 The key idea here is that of the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Working_Set_Size\">working set size<\/a> &#8212; what&#8217;s important is not the total amount of physical or virtual memory being used, but the fraction of that memory that is touched frequently.\u00a0 For example, consider two programs that allocate and use a 1GB array.\u00a0 The first one touches pages within the array at random.\u00a0 The second one touches every page once and then touches the first page many times.\u00a0 The second program will obviously page much less than the first if the system&#8217;s physical memory fills up.<\/p>\n<p>The consequence of this is that a change that reduces the size of data structures that are accessed frequently is much more likely to reduce paging than a change that reduces the size of data structures that are accessed rarely.\u00a0 Note that this is counter-intuitive!\u00a0 It&#8217;s natural to want to optimize data structures that are wasteful of space, but &#8220;wasteful of space&#8221; often means &#8220;hardly touched&#8221; and so such optimizations don&#8217;t have much effect on paging.<\/p>\n<p>Measuring the working set size of a complex program like a web browser is actually rather difficult, which means that gauging the impact of a change on paging is also difficult.\u00a0 Julian Seward&#8217;s <a href=\"http:\/\/blog.mozilla.org\/jseward\/2011\/01\/27\/profiling-the-browsers-virtual-memory-behaviour\/\">virtual memory profiler<\/a> offers one possible way.\u00a0 Another complication is that results vary greatly between machines.\u00a0 If you are running Firefox on a machine with 16GB of RAM, it&#8217;s likely that no change will affect paging, because Firefox is probably never paging in the first place.\u00a0 If you are on a netbook with 1GB of RAM, the story is obviously different.\u00a0 Also, the effects can vary between different operating systems.<\/p>\n<h4>Cache pressure<\/h4>\n<p>Some MemShrink optimizations can also reduce cache pressure.\u00a0 For example, a change that makes a struct smaller would allow more of them to fit into a cache line.\u00a0 Like paging, these effects are very difficult to quantify, and changes that affect hot structures are more likely to reduce cache pressure significantly and improve performance.<\/p>\n<h4>Structure traversals<\/h4>\n<p>Sometimes large data structures must be traversed, and reducing the number of elements in the data structure can reduce that traversal time.\u00a0 The obvious case for Firefox is the JavaScript heap &#8212; the garbage collector and cycle collector frequently traverse it, and so any change that causes dead objects to accumulate more slowly will reduce their traversal times.<\/p>\n<p>Only a small fraction of MemShrink optimizations will speed up structure traversals.<\/p>\n<h3>Stability<\/h3>\n<p>If Firefox (or any program) uses too much memory, it can lead to aborts and crashes.\u00a0 These are sometimes called &#8220;OOMs&#8221; (out of memory). There are two main kinds of OOM:\u00a0 those involving virtual memory, and those involving physical memory.<\/p>\n<h4>Virtual OOMs<\/h4>\n<p>A &#8220;virtual OOM&#8221; occurs when the virtual address space fills up and Firefox simply cannot refer to any more memory.\u00a0 This is mostly a problem on Windows, where Firefox is distributed as a 32-bit application, and so it can only address 2GB or 4GB of memory (the addressable amount depends on the OS configuration).\u00a0 This is true even if you have more than 4GB of RAM.\u00a0 In contrast, Mac OS X and Linux builds of Firefox are 64-bit and so virtual memory exhaustion is essentially impossible because the address space is massively larger.<\/p>\n<p>(I don&#8217;t want to get distracted by the question of why Firefox is a 32-bit application on Windows.\u00a0 I&#8217;ll just mention that (a) many Windows users are still running 32-bit versions of Windows that cannot run 64-bit applications, and (b) Mozilla does 64-bit Windows builds for testing purposes.\u00a0 Detailed discussions of the pros and cons of 64-bit builds can be read <a href=\"https:\/\/groups.google.com\/forum\/?fromgroups#!topic\/mozilla.dev.planning\/Mrba6hvl5-w\">here<\/a> and <a href=\"https:\/\/groups.google.com\/forum\/?fromgroups#!topic\/mozilla.dev.planning\/aeTXSZ_WFAs\">here<\/a>.)<\/p>\n<p>The vast majority of MemShrink optimizations will reduce the amount of virtual memory consumed.\u00a0 (The only counter-examples I can think of involve deliberately evicting pages from physical memory.\u00a0 E.g. see the example of the GC decommitting change discussed below.)\u00a0 And<em><\/em> any such change will obviously reduce the number of virtual OOMs.\u00a0 Furthermore, the effect of any reduction is obvious and straightforward &#8212; a change that reduces the virtual memory consumption by 100MB on a particular machine and workload is twice as good as one that reduces it by 50MB.\u00a0 Of course, any improvement will only be noticed by those who experience virtual OOMs, which typically is people who have 100s of tabs open at once.\u00a0 (It may come as a surprise, but some people have that many tabs open regularly.)<\/p>\n<h4>Physical OOMs<\/h4>\n<p>A &#8220;physical OOM&#8221; occurs when physical memory (and any additional backing storage such as swap space on disk) fills up.\u00a0 This is mostly a problem on low-end devices such as smartphones and netbooks, which typically have small amounts of RAM and may not have any swap space.<\/p>\n<p>The situation for physical memory is similar to that for virtual memory:\u00a0 almost any MemShrink optimization will reduce Firefox&#8217;s physical memory consumption.\u00a0 (One exception is that it&#8217;s possible for a memory allocation to consume virtual memory but not physical memory if it&#8217;s never accessed;\u00a0 more about this in the examples section below.)\u00a0 And any reduction in physical memory consumption will in turn reduce the number of physical OOMs.\u00a0 Finally, the effects are again obvious and straightforward &#8212; a 100MB reduction is twice as good as a 50MB reduction.<\/p>\n<h3>Reputation<\/h3>\n<p>Finally, we have reputation.\u00a0 The obvious effect here is that if MemShrink optimizations cause Firefox to become faster and more stable over time, people&#8217;s opinion of Firefox will rise, either because their own experience improves, or they hear that other people&#8217;s experience improves.<\/p>\n<p>But I want to highlight a less obvious aspect of reputation.\u00a0 People often gauge Firefox&#8217;s memory consumption by looking at a utility such as the Task Manager (on Windows) or &#8216;top&#8217; (on Mac\/Linux).\u00a0 Interpreting the numbers from these utilities is rather difficult &#8212; there are multiple metrics and all sorts of subtleties involved.\u00a0 (See <a href=\"http:\/\/stackoverflow.com\/questions\/1984186\/what-is-private-bytes-virtual-bytes-working-set\">this Stack Overflow post<\/a> for evidence of the complexities and how easy it is to get things wrong.)\u00a0 In fact, in my opinion, the subtleties are so great that people should almost never look at these numbers and instead focus on metrics that are influenced by memory consumption but which they can observe directly as users, i.e. speed and crash rate&#8230; but that&#8217;s a discussion for another time.<\/p>\n<p>Nonetheless, a non-trivial number of people judge Firefox on this metric.\u00a0 Imagine a change that caused Firefox&#8217;s numbers in these utilities to drop but had no other observable effect.\u00a0 (Such a change may be impossible in practice, but that doesn&#8217;t matter in this thought experiment.)\u00a0 One thing that has consistently surprised me is that some people view memory consumption as something approaching a moral issue:\u00a0 low memory consumption is virtuous and high memory consumption is sinful.\u00a0 As a result, this hypothetical change would improve Firefox&#8217;s reputation, rightly or wrongly, for the better.<\/p>\n<p>Let&#8217;s call this aspect of Firefox&#8217;s reputation the &#8220;reputation-by-measurement&#8221;.\u00a0 I suspect the most important metric for reputation-by-measurement is the &#8220;private bytes&#8221; reported by the Windows Task Manager, because that&#8217;s what people seem to most often look at.\u00a0 Private bytes measures the virtual memory of a process that is not shared with any other process.\u00a0 It&#8217;s my educated guess that in Firefox&#8217;s case that the amount of shared memory isn&#8217;t that high, and so the situation is similar to virtual OOMs above &#8212; just about any change that reduces the amount of virtual memory will reduce the private bytes by the same amount, and in terms of reputation-by-measurement, a 100MB reduction is twice as good as a 50MB reduction.<\/p>\n<h3>Examples<\/h3>\n<p>Some examples help bring this discussion together.\u00a0 Consider <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=609905\">bug 609905<\/a>, which removed a 512KB block of memory that was found to be allocated but never accessed.\u00a0 (This occurred because some code that used that block was removed but the allocation wasn&#8217;t removed at the same time.)\u00a0 What were the benefits of this change?<\/p>\n<ul>\n<li>The 512KB never would have been in the working set, so performance would not have been affected.<\/li>\n<li>Virtual memory consumption would have dropped by 512KB, slightly reducing the likelihood of virtual OOMs.<\/li>\n<li>Physical memory consumption probably didn&#8217;t change &#8212; because the block was never accessed, it probably never made it into physical memory.<\/li>\n<li>Private bytes would have dropped by 512KB, slightly improving reputation-by-measurement.<\/li>\n<\/ul>\n<p>Now consider <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=670596\">bug 670596<\/a>, which made the JavaScript garbage collector decommit (i.e. remove from physical memory and backing storage) 1MB heap chunks that are unused.\u00a0 What were the benefits of this change?<\/p>\n<ul>\n<li>Performance may have improved slightly due to reduced paging, on machines where paging happens.\u00a0 Those chunks are clearly not in the working set when they are decommitted, but if paging occurs, the pre-emptive removal of some pages from physical memory may prevent the OS from having to evict some other pages, some of which might have been in the working set.<\/li>\n<li>Virtual memory consumption would not have changed at all, because decommitted memory still takes up address space.<\/li>\n<li>Physical memory consumption would have dropped by the full decommit amount &#8212; 10s or even 100s of MBs in many cases when decommitting is triggered &#8212; significantly reducing the likelihood of physical OOMs.<\/li>\n<li>Private bytes would not have changed, leaving reputation-by-measurement unaffected. <strong>[Update: Justin Lebar queried this. <a href=\"http:\/\/blogs.msdn.com\/b\/ricom\/archive\/2005\/08\/01\/446329.aspx\">This page<\/a> indicates that decommitting memory does reduce private bytes, which means that this change would have improved reputation-by-measurement.]<\/strong><\/li>\n<\/ul>\n<p>Another interesting one is <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=676457\">bug 676457<\/a>.\u00a0 It fixed a problem where PLArenaPool was requesting lots of allocations of ~4,128 bytes.\u00a0 jemalloc rounded these requests up to 8,192 bytes, so almost 50% of each 8,192 byte block was wasted, and there could be many of these blocks.\u00a0 The patch fixed this by reducing the requests to 4,096 bytes which is a power-of-two and not rounded up (and also usually the size of an OS virtual memory page).\u00a0 What were the benefits of this change?<\/p>\n<ul>\n<li>Performance may have improved due to less paging, because the working set size may have dropped.\u00a0 The size of the effect depends how often the final 32 used bytes of each chunk &#8212; those that spilled onto a second page &#8212; are accessed.\u00a0 For at least some of the blocks those 32 bytes would never be touched.<\/li>\n<li>Virtual memory consumption dropped significantly, reducing the likelihood of virtual OOMs.<\/li>\n<li>Physical memory consumption may have dropped, but it&#8217;s not clear by how much.\u00a0 In cases where the extra 32 bytes are never accessed, the second page might not have ever taken up physical memory.<\/li>\n<li>Private bytes would have dropped by the same amount as virtual memory, improving reputation-by-measurement.<\/li>\n<\/ul>\n<p>Finally, let&#8217;s think about a <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=619558\">compacting, generational garbage collector<\/a>, something that is being worked on by the JS team at the moment.<\/p>\n<ul>\n<li>Performance improves for three reasons.\u00a0 First, paging is reduced because of the generational behaviour:\u00a0 much of the JS engine activity occurs in the nursery, which is small;\u00a0 in other words, the memory activity is concentrated within a smaller part of the working set.\u00a0 Second, paging is further reduced because of the compaction: this reduces fragmentation within pages in the tenured heap, reducing the total working set size.\u00a0 Third, the tenured heap grows more slowly because of the generational behaviour: many objects are collected earlier (in the nursery) than they would be with a non-generational collector, which means that structure traversals done by the garbage collector (during full-heap collections) and cycle collector are faster.<\/li>\n<li>Virtual memory consumption drops in two ways.\u00a0 First, the compaction minimizes waste due to fragmentation.\u00a0 Second, the heap grows more slowly.<\/li>\n<li>Physical memory consumption drops for the same two reasons.<\/li>\n<li>Private bytes also drops for the same two reasons.<\/li>\n<\/ul>\n<p>A virtuous change indeed.<\/p>\n<h3>Conclusion<\/h3>\n<p>Reducing Firefox&#8217;s memory consumption is a good thing, and it has the following benefits.<\/p>\n<ul>\n<li>It can improve speed, due to less paging, fewer cache misses, and faster structure traversals.\u00a0 These changes are likely to be noticed more by users on lower-end machines.<\/li>\n<li>It improves stability by reducing virtual OOM aborts, which mostly helps heavy tab users on Windows.\u00a0 It also improves stability by reducing physical OOM aborts, which mostly affects heavy-ish tab users on small devices like smartphones and netbooks.<\/li>\n<li>It improves reputation among those whose browsing experience is improved by the above changes, and also among users who make judgments according to memory measurements with utilities like the Windows Task Manager.<\/li>\n<\/ul>\n<p>Furthermore, when discussing MemShrink optimizations, it&#8217;s a good idea to describe improvements in these terms.\u00a0 For example, instead of saying &#8220;this change reduces memory consumption&#8221;, one could say &#8220;this change reduces physical and virtual memory consumption and may reduce paging&#8221;.\u00a0 I will endeavour to do this myself from now on.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>TL;DR: Any single change that reduces Firefox&#8217;s memory consumption can affect Firefox&#8217;s speed, stability and reputation in a variety of ways, some of which are non-obvious.\u00a0 Some examples illustrate this. The MemShrink wiki page starts with the following text. MemShrink is a project that aims to reduce Firefox&#8217;s memory consumption. There are three potential benefits.\u00a0 [&hellip;]<\/p>\n","protected":false},"author":139,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[30,4544,4546],"tags":[],"_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/posts\/1704"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/users\/139"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/comments?post=1704"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/posts\/1704\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/media?parent=1704"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/categories?post=1704"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/nnethercote\/wp-json\/wp\/v2\/tags?post=1704"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}