Chrome for memory

There are plenty of pictures poking fun at Chrome’s memory eating, and after reading this article you can get a rough idea of why Chrome eats so much and how to get it to eat less.


How does Chrome render a page

This is a Chrome rendering process. The JS code is executed, followed by Style calculation, Layout, Paint, Composite, and finally output a frame to display on the screen. This process is also called pixel pipeline. It is important to note that this pipe is blocked. If the first part is not completed, the next part will not be completed.

So if the page visual changes to achieve 60 FPS (output 60 frames per second), you need to ensure that all the work of a pixel pipeline is completed in 16.7ms. If this time is exceeded, frame loss or stuttering will occur. The optimization of rendering performance is done around these five links. Here are the optimization schemes for each link.


Performance Analysis

Before introducing specific optimization solutions, you need to know how to monitor the time of each phase to find performance bottlenecks.

Chrome provides performance analysis tools: Performance panel. The following picture shows the performance after recording:

As you can see from the image, the tool can record the render FPS, CPU consumption, network consumption, screen per frame, memory consumption, JS stack, DOM number, etc. These snapshot information allows you to analyze the performance at each moment.

Here we focus on the bottom flame diagram, which from left to right is the order in which browser scripts are called, and from top to bottom is the order in which functions are nested. The flame diagram is named because it looks like an upside-down flame. If you look at the flame chart, first look at the task with the longest span, that is, the longest horizontal line, which is the most time-consuming task and also the performance bottleneck point. The browser will automatically mark the task with the longest duration (there will be a red mark in the upper right corner). If you solve the task with the longest duration, you can optimize the performance.


The optimization scheme of each link

The following is an optimization scheme for the five links of pixel pipeline.

The optimization of JavaScript

As we have learned above, the pixel pipeline is blocking, and tasks are interlinked. Poor performance of any task will lead to longer time of the whole pipeline. Therefore, the core logic of performance optimization is to avoid the execution of time-consuming tasks in each link.

First of all, let’s look at what methods JS task execution link has to avoid long tasks. There are the following three schemes:

  1. Using Web Workers to share the stress of pure computing tasks: Pure computing work (which does not require DOM access) is moved to the Web Worker for processing, such as data manipulation and traversal, so that the main thread (usually responsible for UI interaction) is smooth and not blocked or slowed down. See Ruan’s article for more information on Web workers.
  2. Use Time Slicing: Long tasks are sliced up into countless short tasks running in each frame’s requestAnimationFrame. The React16 Fiber architecture uses this optimization method. Why not use the Web Worker to optimize the React16 Fiber architecture? React is used to manipulate the DOM when updating the page, and the Web Worker cannot access the DOM. Here is an example of a long task split:

  1. Use WebAssembly: it is a low-level assembly-like language with a compact binary format. It is faster and smaller because it is binary bytecode (mostly used for video players, audio transcoding tools, web games, encryption and decryption). Refer to the introduction of MDN.

We do not expand the technical details of the above three schemes, but we can summarize their optimization ideas and logic: Web Worker processes JS tasks in parallel for multiple processes; Time Slicing is the fragmentation and execution of time-consuming tasks. WebAssembly is really about improving the execution efficiency of tasks to reduce execution time; All the above methods have achieved the purpose of avoiding long tasks.


Style calculation optimization

Style calculation is the process of calculating the final style of each element according to CSS. It is a process of parsing and matching, so the simpler the CSS selector, the fewer the DOM, the faster the style calculation. The following three methods are designed to achieve the same effect, but the execution speed is successively slower. That is, ID selector is better than Class selector, Class selector is better than pseudo-class selector;However, it should be noted that this stage is generally implemented quickly and will not become a performance bottleneck, avoiding premature and inefficient optimization, and reconstructing the style of the whole project is far less cost-effective than optimizing the JS execution of the project.


Layout optimization

Let’s first understand what layout is doing, and here we illustrate it with the next part of the pixel pipeline:

Just like the picture below, layout is to frame the location and size of each field, and drawing is to plant in each field. If the size of a field changes, the surrounding fields need to be reframed and planted.

By analogy with the above farming, layout is the calculation of how much space an element occupies and its position on the screen. Due to the streaming layout of web pages, changes in the size and position of one element can affect the layout of other elements, so layout often occurs.

This section is most likely to be a performance bottleneck, so the number of layouts and the area affected by the layout should be minimized;

Reduce the number of layouts

If you have to change the CSS properties to get the desired look, try to change the Paint Only properties, such as background images, text colors, or shadows. These changes do not affect the layout of the page, so the browser skips the layout and goes straight to drawing. Note that different browser rendering engines may trigger different cycles for the modification of the same CSS property, which can be found in cssTriger.

Reduce the area of layout influence

A new keyword needs to be mentioned here: layout boundary, any layout changes within the scope of layout boundary elements only need to be “partially rearranged”, by building reasonable layout boundary, can achieve the effect of reducing the influence of layout area; With a few small CSS tweaks, we can enforce layout boundaries in the document. Here are the conditions for building layout boundaries:

Layout boundaries may sound strange at first, but in real development everyone uses them without realizing it; For example, in the figure below, the horizontal scrolling area in the red box forms a layout boundary due to the specified height. Changes in internal elements will only trigger rearrangement in the red box, but not in the area outside the red box.

Avoid forced synchronization of layouts

When JS changes the geometry of a DOM element and then gets the geometry of the element, in order for the browser to answer my question (what is the width), it must do a layout at this point in time. At this point, the layout is synchronous. This is called a forcibly synchronized layout, and this code will trigger a forcibly synchronized layout:

// Set the width
el.style.width = '100px';
// Then ask for it
const width = el.offsetWidth;
Copy the code

If the above code is in a loop, the performance issues are significant;

How to solve this problem: Simply switch the order of the two lines of code.


Draw the optimization

The concept of drawing is easy to understand. Drawing is the process of filling pixels, which are eventually composited onto the user’s screen. However, drawing is not always drawn to a single image in memory. If necessary, drawing will take place on multiple composite Layers. Layers are similar to Layers in Photoshop. The browser merges Layers after each Layer is drawn. Note that this layer is not a Z-index layer and can be viewed in the Layers panel of the console. The following image shows an example of multiple layers:

The optimization idea of drawing is similar to the above layout optimization idea, which is to reduce the area and number of drawing.

Reduce the area to draw

If the page is divided into two layers, when the page changes, only the changing part needs to be drawn, and the unchanged part does not need to be drawn. In this way, the drawing only takes place in a certain Layer, which achieves the purpose of reducing the drawing area.

To create a new layer, add the following CSS attributes to the element:

.moving-element {
	will-change: transform;
	transform: translateZ(0);
}
Copy the code

Reduce the number of times you draw

When implementing animation effects, only transform and opacity should be used as far as possible. Neither layout nor drawing should be used for the modification of these two properties. The browser will skip layout and drawing and only perform composition.

This operation is best suited for high-stress points in the application life cycle, such as animation or scrolling, so animations should be implemented with these two properties.


Layer synthesis optimization

I just mentioned using CSS to create new layers to reduce the number of areas to draw as the page changes, wouldn’t it be good to elevate all elements to one layer? No, layer properly, because each layer requires memory and administrative overhead, which is the key to layer composition optimization: don’t create too many layers. In fact, there is a relationship between drawing and layer composition, which needs to be analyzed in a specific scene to find the best performance balance.


A specific product requirement

Let’s look at a specific product demand scenario and do the actual performance optimization. The functions are as follows: A voice chat room, which will display real-time messages of users in the chat room and automatically scroll the message list to display the latest messages;

The primary programmer began to realize chat room message, after the online user feedback page for a long time mobile phone fever serious page card

Experienced programmers began to do performance optimization, and after the launch, it worked steadily, and the phone was not so hot

Let’s take a look at some of the optimizations made by the veteran programmer with less hair.


Message list optimization scheme

Experienced programmers will recognize this as a performance problem caused by the slow rendering of message lists as more and more messages are sent over a long period of time;

The senior programmer did the following:

  1. Maximum number of messages to be displayed: a maximum of 200 messages are displayed. If the number exceeds 200, old messages are discarded. Ensure that the number of DOM pages will not be infinite expansion, reduce the pressure of layout drawing;
  2. Message interception: 200ms consumes the latest accumulated messages at a time and renders them in batches instead of one by one; Reduce the number of layouts and rearrangements;
  3. Virtual scrolling: Further reduces the number of DOM and reduces the stress of drawing layouts, more on that later;
  4. Image resource reuse: reduce the page operation and storage occupation, detailed in the following;

The first two are easy to understand, but let’s focus on the second two optimizations;

Virtual rolling

In this message list, although many elements of the list are no longer visible on the page, they still undergo rearrangement and redrawing. This part of consumption is unnecessary. Virtual scrolling is to reduce this part of unnecessary consumption. To put it simply, virtual scrolling is to render only the visible area of the list, and use empty DOM or padding as placeholders to reduce performance costs during scrolling or when list elements change. We’re not going to expand this because we don’t have enough space;

There are many open source virtual scroll component libraries on Github, such as vue-virtual-scroll list, react-tiny-virtual-list, vue-virtual-Scroller, etc.

Image resource cropping ladder reuse

The three-party CDN used by the author’s company has the function of stitching parameters and cutting, which can cut the user’s original profile picture according to specific business scenarios and then send it back to the client, thus reducing network consumption.

There are a lot of different sizes of avatars in this page, so is it ok to load resources with the actual size of these images? No, the author has made a step multiplexing here, which is to cut the pictures within a certain size range to the same size, such as 20px to 25px pictures unified use 25px pictures, so that the size of the picture will only request a network resource, the image only exists in memory;

The gap of memory consumption is obvious after a long time of multiplex.

Chrome also has another memory usage mechanism. Chrome puts all resources that have been used on a page in run storage, regardless of whether they have been used later or whether the PREVIOUSLY used DOM has been uninstalled, unless a system-level run collection is triggered, and the run is not used on the JS heap level. Performance is not available. You need to use activity Monitor or Chrome’s task manager to see the actual run consumption. Here is an experiment to illustrate this mechanism.

Open Google Images to search for any image, marked as the start; After scrolling the page to load more pictures, it is marked as terminated; Manually select the body element for deletion, denoted as after manually deleting DOM; Memory performance is as follows:

Record time JS Heap memory Task manager memory
The starting 20-30M 119M
Termination of 22-23M 217M
Manually delete the DOM 20-21M 210M

You can see that Chrome keeps all the resources used by the page in running storage, even though the DOM used by the resource has been uninstalled.


Other chrome environment optimization suggestions

General basic optimization skills, combined with operating environment specific implementation for optimization;

  1. Under the electron:

    1. Non-native dependencies are in devDependence, not dependence;
    2. Call webframe.clearCache () to manually free memory;
  2. Small program:

The characteristics of small program is the separation of rendering thread and JS thread, the advantage of this design is the form of animation stability; The disadvantage is that frequent data interaction between rendering thread and JS thread will become a performance bottleneck. If there are multi-threaded data interaction operations, pay attention to the size of interactive data;