PAIR-code / megaplot

Apache License 2.0
19 stars 5 forks source link

Document Megaplot memory performance best practices #69

Open jimbojw opened 2 years ago

jimbojw commented 2 years ago

Create a document which lists performance best practices such as minimizing memory allocations within callbacks.


It's common in JavaScript, especially when using D3, to use object literals to pass values. Often in the browser, the performance implication of creating these short-lived objects is negligible compared to DOM operations such as reflow and paint.

However, in Megaplot, memory operations are often among the more expensive, and so developers should take care to avoid creating short-lived objects, especially in callbacks which may be invoked many times.

For example, Megaplot allows colors to be specified by their individual component:

const sprite = scene.createSprite();
sprite.enter(s => {
  s.FillColorR = 255;
  s.FillColorG = 0;
  s.FillColorB = 255;
  s.FillColorOpacity = 1;
});

This is verbose, and so Megaplot offers destructuring assignment as well as individual component assignment.

Here's the same example, but using destructuring with an object literal:

const sprite = scene.createSprite();
sprite.enter(s => {
  s.FillColor = [255, 0, 255, 1];
});

Since only a single Sprite is being created, and its enter() callback is invoked only once, this code has the effect of instatiating only one copy of the array [255, 0, 255, 1].

The problem is when the same approach is taken with selections:

const selection = scene.createSelection();
selection.onInit(s => {
  s.FillColor = [255, 0, 255, 1];
});
selection.bind(data);

When the selection is bound to the array of data, the selection's onInit() callback is called for each Sprite/datum pair. That means that a short-lived copy of the array [255, 0, 255, 1] is created and destroyed many times. These allocation and deallocation operations are significantly costlier than mere memory access at scale, and so it's better to declare constants outside of the callbacks like so:

const INIT_FILL_COLOR = [255, 0, 255, 1];
const selection = scene.createSelection();
selection.onInit(s => {
  s.FillColor = INIT_FILL_COLOR;
});
selection.bind(data);