"austin" ++ "writes" ++ "code"

Optimizing V8 Performance in React

March 12, 2018

I was recently reading posts about the V8 JavaScript engine and how it runs your code. This post by Alexander Zlatkov was a really great read and it contains a few tips about strategies to optimize performance. This made me wonder if these affects are something that could ever be noticeable in a client application. I did some tinkering with a React app to see what I could do and wanted to present what I found here.

Class Transitions

The first feature of the V8 engine that Alexander writes about is how it creates classes and transitions objects between them when properties are added. His recommendation is that you always want to add properties to objects in the same order so V8 is able to reuse class transitions that it has created and not force it to make new ones. To test this in React I am rendering 1000 elements and for each one I am creating an object and adding some properties to it.

const Item = () => {
  const thing = {}
  thing.a = 'a'
  thing.b = 'b'
  return <span>{thing.a}</span>
}

class App extends Component {
  render() {
    return (
      <div className="App">
        {Array(1000).fill().map(() => <Item />)}
      </div>
    );
  }
}

Now I will try the same thing but with two functions with different property orders.

const Item = () => {
  const thing = {}
  thing.a = 'a'
  thing.b = 'b'
  return <span>{thing.a}</span>
}

const Item2 = () => {
  const thing = {}
  thing.b = 'b'  
  thing.a = 'a'
  return <span>{thing.a}</span>
}


class App extends Component {
  render() {
    return (
      <div className="App">
        {Array(500).fill().map(() => <Item />)}
        {Array(500).fill().map(() => <Item2 />)}
      </div>
    );
  }
}

After some Chrome dev tools performance analyzing the results are… 167ms to render… For both of them… Exactly the same. I ran the performance analyzer a half dozen times and the average time for the execution of the render function was exactly the same. I can’t say I’m disappointed since I didn’t expect to see any difference but we have to keep trying so how about if I have an object with 3 properties instead of two.

I tried again with a new object and rendered it with all three properties added in the same order and again with 6 different functions that add them in each possible order. This time the results are… 182ms… exactly the same again.

Ok, so it seems like class transitions have no affect on the render of react components. Again I am not surprised. Considering my render also creates a 1000 element array of undefined and creates 1000 React elements I never expected to see a difference. But we won’t admit defeat yet and we will move on to the next optimization technique.

Avoiding Polymorphism

One of the great features of JavaScript is functions can be used polymorphically (is this a real word?) and we can pass them whatever we want. I don’t think Alexander’s post covers this one but I read about it somewhere else. Unfortunately V8 can’t actually pass whatever it wants once the function is compiled so it creates multiple versions of our function for each type of input we pass it and switches for us. Thank you V8. So if we always use functions with the same type of arguments, will we see a difference in performance by helping V8 out and not making it decide for us?

For this I first just made a little add function and this time I render 2000 elements, 1000 by passing numbers to my function and 1000 by passing strings.

function add(x, y) {
    return x + y;
};

const Item = () => <span>{add(1, 2)}</span>;
const Item2 = () => <span>{add('1', '2')}</span>;

class App extends Component {
  render() {
    return (
      <div className="App">
        {Array(1000).fill().map(() => <Item />)}
        {Array(1000).fill().map(() => <Item2 />)}
      </div>
    );
  }
}

Now I am going to render again but use a dedicated string adding function to give V8 a little help.

function add(x, y) {
    return x + y;
};

function addString(x, y) {
    return x + y;
};

const Item = () => <span>{add(1, 2)}</span>;
const Item2 = () => <span>{addString('1', '2')}</span>;

class App extends Component {
  render() {
    return (
      <div className="App">
        {Array(1000).fill().map(() => <Item />)}
        {Array(1000).fill().map(() => <Item2 />)}
      </div>
    );
  }
}

Some more Chrome profiling and the results are… 297ms and 291ms. It improved! Is it much of a difference? No. Is it enough evidence to claim that my two functions actually made a difference? Not Really. Does it make me feel cool to claim that I improved the performance of the render of my react app by optimizing for the V8 engine? Yes.

Honestly I am surprised that there is a 6ms difference. Even with my nasty Array.fills and unnecessary creation of React elements it still shows up in the results.

Closing

I don’t spend much time in the Chrome performance profiler since performance is generally not much of a concern in my daily work. I would imagine it is not a concern for 99% of the people writing React applications, but somewhere out there is someone who has exhausted all other methods for performance and is optimizing for the compiler. For them, these strategies can be the difference that makes their app successful and it was fun to step into their shoes for about an hour this afternoon.

As always there is a relevant xkcd to remind you that you are probably not the 1% of people that need to think about this stuff.

relevant xkcd


Austin Willis

Written by Austin Willis who lives in Saint Louis and builds things on the internet. You can follow Austin on Twitter