1 of 53

A Thorough Study

of Blink’s Memory

in Real-world Websites

*

*

*

*

{haraken, bashi, hajimehoshi, tasak}@chromium.org

2015 Oct

2 of 53

Memory team projects

  • Oilpan
    • haraken@, keishi@, peria@, yutak@

  • Memory reduction (⇐ This talk is about this)
    • bashi@, hajimehoshi@, tasak@

  • Instant tab restore
    • kouhei@, tzik@

*

*

3 of 53

Memory reduction

  • As of 2015 June, memory reduction was raised as one of the priorities of the web-platform team

  • At that point, no one knew even what percentage of renderer’s memory is consumed by Blink

  • Since then, we’ve been working on understanding Blink’s memory usage and identifying sweet spots to reduce it

*

*

4 of 53

Goals of this talk

  • Goal 1: Share our findings about Blink’s memory

  • Goal 2: Propose key projects that (we think) will reduce Blink’s memory

  • Goal 3: Get more teams involved in the memory reduction efforts

*

*

5 of 53

Understand real-world memory usage

6 of 53

What we have done

  • Step 1: Improve tooling (memory-infra)

  • Step 2: Understand memory usage of real-world websites

*

*

7 of 53

Improve tooling

  • As of 2015 June:
    • Blink mixed four memory allocators
      • PartitionAlloc
      • Oilpan
      • tcmalloc
      • system allocator
    • There was no way to profile Blink’s memory

*

*

8 of 53

Improve tooling

  • We (mostly) removed tcmalloc and system allocators from Blink
    • PartitionAlloc
    • Oilpan
    • tcmalloc
    • system allocator

*

*

9 of 53

Improve tooling

  • The London team supported PartitionAlloc and Oilpan in memory-infra

*

*

10 of 53

Improve tooling

  • We implemented a per-object-type profiler, which can list up objects frequently allocated

*

*

Object type

Size

Count

Vector<RuleData>

12256 byte

3

Vector<RuleFeature>

800 byte

6

ImmutableStylePropertySet

26 byte

2112

...

...

...

11 of 53

Improve tooling

  • The London team and we are implementing an allocation-site profiler, which can visualize stack traces of frequently allocated objects
    • The profiler will be integrated to memory-infra soon

StringImpl 536 KB

<--- AtomicString::AtomicString()

<--- ScriptResource::script()

*

*

12 of 53

Improve tooling

  • Summary:
    • Now Blink uses only PartitionAlloc and Oilpan
    • memory-infra visualizes memory usage of PartitionAlloc and Oilpan
    • More detailed profilers (per-object-type profiler & allocation-site profiler) are going to be added to memory-infra

*

*

13 of 53

Understand real worlds

  • Now we have great tools :)

  • The next step is to understand memory usage of real-world websites

  • Notes about the results shown in this slide:
    • They are measured on a device without low-RAM mode. You can find more results in this spreadsheet.
    • They are measured after 10 seconds after a page load.

*

*

14 of 53

Understand real worlds

  • Break down the memory
    • Break down the renderer’s memory
      • Renderer = V8 + PartitionAlloc + Oilpan + ...
    • Break down the PartitionAlloc’s memory
      • PartitionAlloc = Buffer + FastMalloc + Node + Layout
    • Break them down into per-object memory
      • Buffer = StringImpl + Vector + HashTable + ...

*

*

15 of 53

Break down of renderer’s memory

*

*

Page

Total (*1)

PartitionAlloc

Oilpan

V8

Skia

Discardable

cc

pinterest.com

160.2 MB

9.7%

1.1%

15.7%

15.0%

48.3%

10.1%

facebook.com

47.2 MB

18.0%

1.9%

50.6%

6.1%

8.5%

15.0%

theverge.com

115.9 MB

16.7%

1.2%

45.1%

11.4%

17.3%

8.3%

worldjournal.com

261.5 MB

9.9%

0.6%

77.1%

4.3%

4.6%

3.5%

wikipedia.org

44.8 MB

9.6%

2.2%

44.7%

4.0%

8.9%

30.4%

reddit.com

45.9 MB

13.7%

2.2%

43.6%

5.7%

8.7%

25.6%

wordpress.com

111.4 MB

2.5%

0.9%

9.1%

19.7%

55.7%

12.2%

plus.google.com

156.9 MB

17.8%

0.8%

60.3%

2.7%

7.7%

9.4%

blogspot.com

47.3 MB

11.8%

2.1%

44.2%

4.9%

8.5%

28.5%

mail.google.com

114.7 MB

14.7%

0.9%

66.2%

1.2%

3.5%

11.8%

16 of 53

Break down of renderer’s memory

  • The largest consumer is V8
    • V8 consumes 40 - 70% in common cases

  • The second largest consumer is Blink (PartitionAlloc+Oilpan)
    • Blink consumes 10 - 20% in common cases

  • Some (a lot?) of the memory in Discardable, CC and V8 is retained by Blink

*

*

17 of 53

Break down of PartitionAlloc’s memory

  • Preliminary knowledge:
    • Since we’ve not yet fully shipped Oilpan, most Blink objects are allocated in PartitionAlloc
    • PartitionAlloc has four partitions:
      • Node (for Nodes)
      • Layout (for LayoutObjects)
      • Buffer (for Vectors, HashTables, StringImpls, ArrayBuffers etc)
      • FastMalloc (for SharedBuffers and all other objects)

*

*

18 of 53

Break down of PartitionAlloc’s memory

*

*

Page

Total of PartitionAlloc

Buffer

FastMalloc

Node

Layout

pinterest.com

15.6 MB

70.5%

26.9%

1.4%

1.3%

facebook.com

8.5 MB

72.9%

21.2%

2.9%

2.2%

theverge.com

19.3 MB

56.0%

33.2%

4.9%

6.2%

worldjournal.com

25.9 MB

49.8%

41.3%

4.6%

4.3%

wikipedia.org

4.3 MB

37.2%

44.2%

8.4%

10.1%

reddit.com

6.3 MB

52.4%

33.3%

6.0%

8.3%

wordpress.com

2.8 MB

42.9%

46.4%

5.7%

6.1%

plus.google.com

27.9 MB

64.5%

32.6%

1.2%

1.5%

blogspot.com

5.6 MB

62.5%

30.4%

5.0%

2.9%

mail.google.com

16.9 MB

68.1%

28.4%

1.9%

1.6%

19 of 53

Break down of PartitionAlloc’s memory

  • The largest consumer is the Buffer partition
    • StringImpl, Vector, HashTable, ArrayBuffer etc

  • The second largest consumer is the FastMalloc partition
    • SharedBuffer, all DOM objects except for Node and LayoutObject

  • sizeof(Node) and sizeof(LayoutObject) don’t really matter

*

*

20 of 53

Break down of the Buffer partition

*

*

Page

Total of the Buffer partition

>50 KB StringImpls

<50 KB StringImpls

Vectors

HashTables

Others

pinterest.com

11 MB

76.4%

2.7%

3.0%

11.8%

6.0%

facebook.com

6.2 MB

71.0%

4.8%

1.7%

13.0%

9.5%

theverge.com

10.8 MB

29.6%

11.1%

25.0%

15.7%

18.6%

worldjournal.com

12.9 MB

14.0%

32.6%

23.3%

20.2%

20.9%

wikipedia.org

1.6 MB

14.7%

9.2%

10.8%

35.8%

29.5%

reddit.com

3.3 MB

22.0%

8.3%

20.0%

27.0%

22.7%

wordpress.com

1.2 MB

0%

12.7%

10.6%

30.4%

46.3%

plus.google.com

18.0 MB

39.4%

3.3%

28.3%

20.0%

9.0%

blogspot.com

3.5 MB

51.4%

5.7%

13.7%

14.6%

14.6%

mail.google.com

11.5 MB

40.9%

16.5%

14.8%

15.7%

12.1%

21 of 53

Break down of the Buffer partition

  • The largest consumer is StringImpls larger than 50 KB
    • We confirmed that more than 90% of the large StringImpls are JavaScript source code

  • The second largest consumer is Vectors and HashTables
    • We don’t yet know where they come from

*

*

22 of 53

Break down of the FastMalloc partition

*

*

Page

Total of the FastMalloc partition

SharedBuffers

Other objects

pinterest.com

4.2 MB

35.7%

64.3%

facebook.com

1.8 MB

24.0%

76.0%

theverge.com

6.4 MB

25.0%

75.0%

worldjournal.com

10.7 MB

46.7%

53.3%

wikipedia.org

1.9 MB

17.1%

72.9%

reddit.com

2.1 MB

29.5%

70.5%

wordpress.com

1.3 MB

29.3%

70.7%

plus.google.com

9.1 MB

12.1%

87.9%

blogspot.com

1.7 MB

22.1%

77.9%

mail.google.com

4.8 MB

5.6 %

94.4%

23 of 53

Break down of the FastMalloc partition

  • The largest consumer is SharedBuffers
    • They are used as backing storage of Resources

  • Other objects have a very long tail
    • The 2nd - 5th largest consumers depend on webpages
    • Reducing sizeof(DOM object) is not likely to contribute to reducing Blink’s memory usage

*

*

24 of 53

Summary

  • Blink (PartitionAlloc+Oilpan) is the second largest consumer of renderer’s memory
    • Blink consumes 10 - 20% in common cases
    • Blink retains some of memory in Discardable, CC and V8

  • Inside Blink, the main memory consumers are:
    • Large StringImpls (used by JavaScript source code)
    • SharedBuffers (used by Resources)
    • Vectors and HashTables

*

*

25 of 53

Goal & Approach

26 of 53

Goal

  • Based on the investigation, we defined key 10 pages where Blink’s memory reduction is a key

*

*

Page

Total

PartitionAlloc’s rate

pinterest.com

160.2 MB

9.7%

facebook.com

47.2 MB

18.0%

theverge.com

115.9 MB

16.7%

worldjournal.com

261.5 MB

9.9%

wikipedia.org

44.8 MB

9.6%

reddit.com

45.9 MB

13.7%

wordpress.com

111.4 MB

2.5%

plus.google.com

156.9 MB

17.8%

blogspot.com

47.3 MB

11.8%

mail.google.com

114.7 MB

14.7%

27 of 53

Goal

  • Our goal is to reduce Blink’s memory in the key 10 pages by 30% by 2016 Q1

*

*

28 of 53

Goal

  • “Blink’s memory” refers to the following two things:
    • The amount of memory consumed by PartitionAlloc and Oilpan
    • The amount of memory consumed by V8, CC and Skia retained by Blink
      • e.g., Blink objects can have a strong reference to a DOM wrapper which retains the entire window object

*

*

29 of 53

Goal

  • Should we focus on short-lived apps? Or long-lived apps?
    • For short-lived apps, memory after 10 seconds after a page load matters
    • For long-lived apps, memory after 1 hour after a page load & a lot of user interactions matters

  • For now we’re focusing on short-lived apps because short-lived apps would be more important in mobile devices

*

*

30 of 53

Approach

  • OK, the goal is now clear:
    • Reducing Blink’s memory by 30%

  • How can we achieve the goal?
    • MemoryPurgeController

*

*

31 of 53

Approach

  • When Blink should save memory, MemoryPurgeController dispatches MemoryPurgeClient::purgeMemory()

class MemoryPurgeController {

void dispatchPurgeMemory(...) { // Dispatched when Blink should save memory

for (auto& client : m_clients) // Our goal is to purge Blink’s memory by 30%

client->purgeMemory(...);

}

HashSet<MemoryPurgeClient*> m_clients;

};

class MemoryCache : public MemoryPurgeClient {

void purgeMemory(...) override { pruneAll(); } // Example

};

*

*

32 of 53

Approach

  • When does MemoryPurgeController dispatch the purgeMemory()?
    • When X seconds have passed since a tab became inactive
    • When Blink receives MemoryPressureListener’s notifications

*

*

33 of 53

Approach

  • What should the purgeMemory() do?
    • It is NOT a good idea to implement purgeMemory() on various caches just because they are discardable
      • It will just increase code complexity
    • It is important to identify caches that have an impact on Blink’s total memory and implement purgeMemory() only on them

*

*

34 of 53

What we’ve learned so far

  • Memory reduction based on a “guess” is NOT likely to contribute to the reduction of Blink’s total memory
    • We experimented with discarding Blink items each TL listed in this spreadsheet, but most of them didn’t have an impact on Blink’s total memory
    • Also reducing sizeof(DOM object) won’t have an impact

*

*

35 of 53

What we’ve learned so far

  • It is important to drive the memory reduction more tactically
    • Step 1: Fix low-hanging fruits
    • Step 2: Identify key projects (sweet spots)
    • Step 3: Reduce the memory at a pinpoint

  • What would the key projects be?

*

*

36 of 53

4 key projects

37 of 53

4 key projects

*

*

38 of 53

Compression of large StringImpls

  • The largest consumer of PartitionAlloc is large StringImpls

  • More than 90% of the large StringImpls are JavaScript source code
    • They are not discardable (used by V8)
    • The only option to reduce the memory is to compress the StringImpls

*

*

39 of 53

Compression of large StringImpls

  • We confirmed that if we gzip large StringImpls, we can reduce PartitionAlloc’s memory by 10 - 60% (17% in average)

*

*

Page

Reduction rate

pinterest.com

63%

facebook.com

15%

theverge.com

11%

worldjournal.com

3%

wikipedia.org

19%

reddit.com

10%

wordpress.com

14%

plus.google.com

5%

blogspot.com

9%

mail.google.com

21%

40 of 53

More aggressive Resource pruning

  • The second largest consumer of PartitionAlloc is SharedBuffers

  • SharedBuffers are used as backing storage of Resources
    • Memory reduction on Resources is important

*

*

41 of 53

More aggressive Resource pruning

  • Actually, memory reduction on Resources is more important than the data implies because Resources retain memory in Discardable and Skia as well
    • Some Resources use locked discardable memory
    • Image/FontResources can hold caches in Skia

*

*

42 of 53

More aggressive Resource pruning

  • Low-hanging fruits:

*

*

43 of 53

More aggressive Resource pruning

  • More aggressive idea:
    • Forcibly discard all Resources from an inactive tab
    • When the tab is re-activated, fetch the Resource again (hopefully from the network cache) in a way that doesn’t dispatch onload events

*

*

44 of 53

Discarding layout trees

  • LayoutObject seems to retain a lot of memory in CC
    • CC consumes 10 - 30% of renderer’s memory
    • Though LayoutObject itself consumes only 1- 10% of PartitionAlloc

  • Idea:
    • Discard layout trees from an inactive tab
    • We can recalculate the layout tree when requested

*

*

45 of 53

Making V8 ⇔ Blink cycles collectable

  • V8 is the largest consumer of renderer’s memory
    • V8 consumes 40 - 70% of renderer’s memory

  • At the very least, Blink should not be the culprit of V8 memory leaks

*

*

46 of 53

Making V8 ⇔ Blink cycles collectable

  • Example: e = new CustomEvent("foo", {detail: new Error()});
  • This was a cause of a lot of window leaks in Polymer apps

*

*

47 of 53

Making V8 ⇔ Blink cycles collectable

  • A V8 object has a strong reference to the window object that created the V8 object
  • Thus if a Blink object retains a v8::Persistent handle to the V8 object, it retains the entire window object...

*

*

48 of 53

Making V8 ⇔ Blink cycles collectable

  • Idea: Remove all v8::Persistent handles by making V8 ⇔ Blink cycles collectable
    • All V8 leaks caused by Blink will be gone

*

*

49 of 53

Summary

  • 4 key projects:

  • We want to delegate the reduction work to each team (i.e., experts :-)

*

*

50 of 53

Getting more teams involved

51 of 53

Getting more teams involved

  • Memory reduction is the priority of the web-platform team
  • We want to get more teams involved on the reduction efforts
  • Then we can move faster

*

*

52 of 53

Getting more teams involved

  • That said, it is NOT a good idea to start memory reduction based on a “guess”
    • Reducing sizeof(DOM object) won’t have an impact
    • Discarding caches won’t have an impact in most cases

*

*

53 of 53

Getting more teams involved

  • So the memory team will focus on:
    • Improving tooling
    • Understanding Blink’s memory usage
    • Identifying sweet spots that will have a large impact

  • We may delegate the actual reduction efforts to the team that owns the area
    • We’ll kick off a discussion soon :)

*

*