How 3 Lines of Code Reduced CPU and Memory Consumption by 13%
Tags: javascript, angular, pixijs, performance
Published: 2019-01-08 13:55:27

---
title: How 3 Lines of Code Reduced CPU and Memory Consumption by 13%
published: true
tags: javascript,angular,pixijs,performance
canonical_url: https://medium.com/@hazterisk/how-3-lines-of-code-reduced-cpu-and-memory-consumption-by-13-b451de874701
---

Or how assumptions can ruin the performance of your app.

We have a fairly large Angular app using PixiJS for a lot of the rendering. Due to animations and constant updates to the canvas area, we’ve had to optimize as much as possible to keep the app running reasonably well on tablets.

The standard optimizations have all been applied.

Almost all components are set to `ChangeDetectionStrategy.onPush`, async and broadcasts use `runOutsideAngular`, logging is suppressed in production, etc.

We’ve leveraged PixiJS texture cache and don’t make any `.update()` calls on any display objects, and the `render()` call on `requestAnimationFrame` is run outside angular.

One oversight is size of certain call stacks when major data changing events occur, but other than that things seem fairly contained.

And yet we still had users noticing performance issues.

Lots of ideas for this. Use web workers for transport, distribute chunks of functionality across separate call stacks to reduce frame drop, make absolutely all components use `ChangeDetectionStrategy.onPush`, detach components and reattach for updates…

A number of investigations into Angular and PixiJS performance issues were made.

Then, while investigating the memory and timing costs of angular binding, RxJS subjects, and Angular bound subjects, I created a quick Angular CLI app. After setting everything up I checked a performance recording and noticed a distinct shortage of change detection runs.

I should mention here that in order to keep our app’s frame rate decent we use `requestAnimationFrame` on a loop to render the PixiJS stage. What we didn’t realize was that each RAF call was triggering change detection.

But why?

### Understanding How Your Tools Work is Important

Enter Zone.js. I challenge any Angular developer to explain how Zone.js works and how Angular leverages Zones.

Here’s the short version: Zone.js changes the prototypes of all async calls in the browser api (`setTimeout`, etc.) so that a) context can be shared across call stacks and b) that hooks can be emitted at the end of micro, macro, and event tasks.

Cool. And Angular?

Angular creates (forks) a new zone called `NgZone` which is a child of the root zone. This way when those end-of-task hooks are emitted, angular can run change detection on the off chance that a call has resulted in something, somewhere, changing. Which is great when you don’t have a ton of components. Not so much when you have lots.

And I’m not knocking Angular’s change detection. given the size of our app it was actually doing an impressive job running.

This issue is with the memory allocations required to run it. We ended up getting 12Mb garbage collections every 900ms.

Ok, and PixiJS?

Any event listener you register to a PixiJS object doesn’t hit the browser api. It hits PixiJS’s custom handling of events via their `InteractionManager`. And it’s the `InteractionManager` that registers a `pointermove` event to the document… which has been patched by Zone …which has been forked by Angular.

Ok. So our third party library is triggering change detection in our framework on mouse move — another problem — but that still doesn’t explain what’s triggering CD on every animation frame.

_side note update: we resolved this using `__Zone_symbol__BLACK_LISTED_EVENTS`_

### Seriously Now, Understanding How Your Tools Work is Important

PixiJS has a nice way of handling the scenario of a `DisplayObject` animating past your mouse. Under normal conditions you wouldn’t get a `mouseover` event because the mouse didn’t move, ergo no event.

PixiJS is clever though. It’s using that `pointermove` event to cache the last pointer event. Then on the next tick of pixi’s event loop it will check a moving `DisplayObject`’s position against the cached pointer event’s location, and fire a `mouseover` event if the display object is under the mouse.

Ok, next tick of Pixi’s event loop though?

Turns out in order to animate all their `DisplayObjects` they have a shared ticker running … wait for it … as a callback in `requestAnimationFrame`.

So now we not only have our render loop happening 60 times a second, we also have Pixi’s ticker triggering change detection 60 times a second. Not awesome.

First prize is to get PixiJS to run in the root zone instead of Angular’s `NgZone`, but in the meantime these 3 lines of code stop all the unnecessary change detection runs:

```javascript
const ticker = Pixi.shared.ticker; 
ticker.autoStart = false; 
ticker.stop();
```

We now have 1/3 of the garbage collections as before.