perf(ui): use rfdc for deep copying of objects

- Add and use more performant `deepClone` method for deep copying throughout the UI.

Benchmarks indicate the Really Fast Deep Clone library (`rfdc`) is the best all-around way to deep-clone large objects.

This is particularly relevant in canvas. When drawing or otherwise manipulating canvas objects, we need to do a lot of deep cloning of the canvas layer state objects.

Previously, we were using lodash's `cloneDeep`.

I did some fairly realistic benchmarks with a handful of deep-cloning algorithms/libraries (including the native `structuredClone`). I used a snapshot of the canvas state as the data to be copied:

On Chromium, `rfdc` is by far the fastest, over an order of magnitude faster than `cloneDeep`.

On FF, `fastest-json-copy` and `recursiveDeepCopy` are even faster, but are rather limited in data types. `rfdc`, while only half as fast as the former 2, is still nearly an order of magnitude faster than `cloneDeep`.

On Safari, `structuredClone` is the fastest, about 2x as fast as `cloneDeep`. `rfdc` is only 30% faster than `cloneDeep`.

`rfdc`'s peak memory usage is about 10% more than `cloneDeep` on Chrome. I couldn't get memory measurements from FF and Safari, but let's just assume the memory usage is similar relative to the other algos.

Overall, `rfdc` is the best choice for a single algo for all browsers. It's definitely the best for Chromium, by far the most popular desktop browser and thus our primary target.

A future enhancement might be to detect the browser and use that to determine which algorithm to use.
This commit is contained in:
psychedelicious
2024-04-02 22:59:07 +11:00
committed by Kent Keirsey
parent a6c91979af
commit 69ec14c7bb
14 changed files with 100 additions and 43 deletions

View File

@ -1,4 +1,5 @@
import { cloneDeep, merge } from 'lodash-es';
import { deepClone } from 'common/util/deepClone';
import { merge } from 'lodash-es';
import { ClickScrollPlugin, OverlayScrollbars } from 'overlayscrollbars';
import type { UseOverlayScrollbarsParams } from 'overlayscrollbars-react';
@ -22,7 +23,7 @@ export const getOverlayScrollbarsParams = (
overflowX: 'hidden' | 'scroll' = 'hidden',
overflowY: 'hidden' | 'scroll' = 'scroll'
) => {
const params = cloneDeep(overlayScrollbarsParams);
const params = deepClone(overlayScrollbarsParams);
merge(params, { options: { overflow: { y: overflowY, x: overflowX } } });
return params;
};

View File

@ -0,0 +1,15 @@
import rfdc from 'rfdc';
const _rfdc = rfdc();
/**
* Deep-clones an object using Really Fast Deep Clone.
* This is the fastest deep clone library on Chrome, but not the fastest on FF. Still, it's much faster than lodash
* and structuredClone, so it's the best all-around choice.
*
* Simple Benchmark: https://www.measurethat.net/Benchmarks/Show/30358/0/lodash-clonedeep-vs-jsonparsejsonstringify-vs-recursive
* Repo: https://github.com/davidmarkclements/rfdc
*
* @param obj The object to deep-clone
* @returns The cloned object
*/
export const deepClone = <T>(obj: T): T => _rfdc(obj);