Skip to content

Commit

Permalink
perf(strict-shallow-copying): enhancing the performance of strict sha…
Browse files Browse the repository at this point in the history
…llow copying (#31)
  • Loading branch information
unadlib committed Jan 23, 2024
1 parent cfc3a37 commit c775125
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 5 deletions.
Binary file added benchmark-class.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"benchmark:base": "NODE_ENV='production' ts-node test/performance/benchmark.ts",
"benchmark:object": "NODE_ENV='production' ts-node test/performance/benchmark-object.ts",
"benchmark:array": "NODE_ENV='production' ts-node test/performance/benchmark-array.ts",
"benchmark:class": "NODE_ENV='production' ts-node test/performance/benchmark-class.ts",
"performance:basic": "cd test/performance && NODE_ENV='production' ts-node index.ts",
"performance:set-map": "cd test/performance && NODE_ENV='production' ts-node set-map.ts",
"performance:big-object": "cd test/performance && NODE_ENV='production' ts-node big-object.ts",
Expand Down
15 changes: 10 additions & 5 deletions src/utils/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@ import { dataTypes } from '../constant';
import { getValue, isDraft, isDraftable } from './draft';

function strictCopy(target: any) {
const descriptors = Object.getOwnPropertyDescriptors(target);
Reflect.ownKeys(descriptors).forEach((key: any) => {
const desc = descriptors[key];
const copy = Object.create(Object.getPrototypeOf(target));
Reflect.ownKeys(target).forEach((key: any) => {
let desc = Reflect.getOwnPropertyDescriptor(target, key)!;
if (desc.enumerable && desc.configurable && desc.writable) {
copy[key] = target[key];
return;
}
// for freeze
if (!desc.writable) {
desc.writable = true;
desc.configurable = true;
}
if (desc.get || desc.set)
descriptors[key] = {
desc = {
configurable: true,
writable: true,
enumerable: desc.enumerable,
value: target[key],
};
Reflect.defineProperty(copy, key, desc);
});
return Object.create(Object.getPrototypeOf(target), descriptors);
return copy;
}

const propIsEnum = Object.prototype.propertyIsEnumerable;
Expand Down
199 changes: 199 additions & 0 deletions test/performance/benchmark-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* eslint-disable import/no-relative-packages */
/* eslint-disable prefer-template */
// @ts-nocheck
import fs from 'fs';
import https from 'https';
import { Suite } from 'benchmark';
import { parse } from 'json2csv';
import deepFreeze from 'deep-freeze';
import QuickChart from 'quickchart-js';
import {
produce,
enablePatches,
produceWithPatches,
setAutoFreeze,
immerable,
} from 'immer';
// import {
// produce,
// enablePatches,
// produceWithPatches,
// setAutoFreeze,
// } from '../../../temp/immer/dist';
import { create } from '../..';

const config: Parameters<QuickChart['setConfig']>[0] = {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Mutative',
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgba(54, 162, 235, 0.5)',
data: [],
fill: false,
},
// {
// label: 'Naive handcrafted reducer',
// backgroundColor: 'rgba(255, 0, 0, 0.5)',
// borderColor: 'rgba(255, 0, 0, 0.5)',
// data: [],
// fill: false,
// },
{
label: 'Immer',
backgroundColor: 'rgba(0, 255, 0, 0.5)',
borderColor: 'rgba(0, 255, 0, 0.5)',
data: [],
fill: false,
},
],
},
options: {
title: {
display: true,
text: 'Mutative vs Immer performance - class',
},
scales: {
xAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: 'Number of class keys',
},
},
],
yAxes: [
{
display: true,
scaleLabel: {
display: true,
labelString: 'Measure(seconds), lower is better',
},
},
],
},
},
};

const run = (size: number) => {
config.data.labels.push(size);
class Foo {
[immerable] = true;
}
const getData = (size: number) =>
Array(size)
.fill(1)
.reduce(
(acc, _, key) => Object.assign(acc, { [`key${key}`]: key }),
new Foo() as Record<string, number>
);

const suite = new Suite();

let i: number;
let baseState: Record<string, number>;

suite
.add(
'Mutative',
() => {
const state = create(
baseState,
(draft) => {
draft.key0 = i;
},
{
mark: (target, { immutable }) => {
if (target[immerable]) {
return immutable;
}
},
}
);
},
{
onStart: () => {
i = Math.random();
baseState = getData(size);
},
}
)
// .add(
// 'Naive handcrafted reducer',
// () => {
// const state = {
// ...baseState,
// key0: i,
// };
// },
// {
// onStart: () => {
// i = Math.random();
// baseState = getData(size);
// },
// }
// )
.add(
'Immer',
() => {
const state = produce(baseState, (draft: any) => {
draft.key0 = i;
});
},
{
onStart: () => {
i = Math.random();
baseState = getData(size);
},
}
)
.on('cycle', (event) => {
const data = config.data.datasets.find(
(item) => item.label === event.target.name
);
data.data.push(1000 / event.target.hz);
})
.on('complete', function () {
console.log(
`Size ${size}: The fastest method is ${this.filter('fastest').map(
'name'
)}`
);
})
.run({ async: false });
};

[
...Array(9)
.fill(1)
.map((_, i) => (i + 1) * 10 ** 1),
...Array(9)
.fill(1)
.map((_, i) => (i + 1) * 10 ** 2),
// ...Array(9)
// .fill(1)
// .map((_, i) => (i + 1.5) * 10 ** 3),
// ...Array(9)
// .fill(1)
// .map((_, i) => (i + 1) * 10 ** 4),
]
.sort((a, b) => a - b)
.forEach((value) => run(value));

try {
const chart = new QuickChart();
chart.setConfig(config);
const file = fs.createWriteStream('benchmark-class.jpg');
https.get(chart.getUrl(), (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
console.log('update benchmark-class');
});
});
} catch (err) {
console.error(err);
}

0 comments on commit c775125

Please sign in to comment.