Skip to content

Commit

Permalink
test: add benchmark tests to splitProps and mergeProps (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrgm committed May 11, 2023
1 parent a621605 commit 4bc350b
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 0 deletions.
144 changes: 144 additions & 0 deletions packages/solid/test/component.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { mergeProps, splitProps } from "../src";
import * as old from "./component.old";
import { bench } from "vitest";

const staticDesc = {
value: 1,
writable: true,
configurable: true,
enumerable: true,
};
const signalDesc = {
get() {
return 1;
},
configurable: true,
enumerable: true,
};
const createObject = (
prefix: string,
amount: number,
desc: (index: number) => PropertyDescriptor
) => {
const proto: Record<string, any> = {};
for (let index = 0; index < amount; ++index)
proto[`${prefix}${index}`] = desc(index);
return Object.defineProperties({}, proto) as Record<string, any>;
};

const keys = (o: Record<string, any>) => Object.keys(o);

type Test = {
title: string;
benchs: { title: string; func: any }[];
};

function createTest<
T extends (...args: any[]) => any,
G extends (...args: any[]) => any
>(options: {
subjects: {
name: string;
func: T;
}[];
generator: Record<string, G>;
inputs: (generator: G) => Record<string, Parameters<T>>;
}) {
const tests: Test[] = [];
for (const generatorName in options.generator) {
const generator = options.generator[generatorName];
const inputs = options.inputs(generator);
for (const title in inputs) {
const args = inputs[title];
const test: Test = { title: `${title} (${generatorName})`, benchs: [] };
for (const subject of options.subjects) {
test.benchs.push({
title: subject.name,
func: () => subject.func(...args),
});
}
tests.push(test);
}
}
return tests;
}

type SplitProps = (...args: any[]) => Record<string, any>[]

const generator = {
static: (amount: number) =>
createObject("static", amount, () => staticDesc),
dynamic: (amount: number) =>
createObject("dynamic", amount, () => signalDesc),
mixed: (amount: number) =>
createObject("mixed", amount, (v) => (v % 2 ? staticDesc : signalDesc)),
} as const

const splitPropsTests = createTest({
subjects: [
{
name: "splitProps",
func: splitProps as SplitProps,
},
{
name: "oldSplitProps",
func: old.splitProps as SplitProps,
},
],
generator,
inputs: (g) => ({
"splitProps(5, 1)": [g(5), keys(g(1))],
"splitProps(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))],
"splitProps(0, 15)": [g(0), keys(g(15))],
"splitProps(0, 3, 2)": [g(0), keys(g(3)), keys(g(2))],
"splitProps(0, 100)": [g(0), keys(g(100))],
"splitProps(0, 100, 3, 2)": [g(0), keys(g(100)), keys(g(3)), keys(g(2))],
"splitProps(25, 100)": [g(25), keys(g(100))],
"splitProps(50, 100)": [g(50), keys(g(100))],
"splitProps(100, 25)": [g(100), keys(g(25))],
}),
});

const mergePropsTest = createTest({
subjects: [
{
name: "mergeProps",
func: mergeProps,
},
{
name: "oldMergeProps",
func: old.mergeProps ,
},
],
generator,
inputs: (g) => ({
"mergeProps(5, 1)": [g(5), (g(1))],
"mergeProps(5, 1, 2)": [g(5), (g(1)), (g(2))],
"mergeProps(0, 15)": [g(0), (g(15))],
"mergeProps(0, 3, 2)": [g(0), (g(3)), (g(2))],
"mergeProps(0, 100)": [g(0), (g(100))],
"mergeProps(0, 100, 3, 2)": [g(0), (g(100)), (g(3)), (g(2))],
"mergeProps(25, 100)": [g(25), (g(100))],
"mergeProps(50, 100)": [g(50), (g(100))],
"mergeProps(100, 25)": [g(100), (g(25))],
}),
});

const iterations = 100

for (const test of splitPropsTests) {
describe(test.title, () => {
for (const { title, func } of test.benchs) bench(title, func, {
iterations
});
});
}


for (const test of mergePropsTest) {
describe(test.title, () => {
for (const { title, func } of test.benchs) bench(title, func, {
iterations
});
});
}
161 changes: 161 additions & 0 deletions packages/solid/test/component.old.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { $PROXY, MergeProps, SplitProps, createMemo } from "../src";
import { EffectFunction } from "../types";

function trueFn() {
return true;
}

function resolveSource(s: any) {
return !(s = typeof s === "function" ? s() : s) ? {} : s;
}

const propTraps: ProxyHandler<{
get: (k: string | number | symbol) => any;
has: (k: string | number | symbol) => boolean;
keys: () => string[];
}> = {
get(_, property, receiver) {
if (property === $PROXY) return receiver;
return _.get(property);
},
has(_, property) {
if (property === $PROXY) return true;
return _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: true,
enumerable: true,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
export function splitProps<
T extends Record<any, any>,
K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]]
>(props: T, ...keys: K): SplitProps<T, K> {
const blocked = new Set<keyof T>(keys.length > 1 ? keys.flat() : keys[0]);
if ($PROXY in props) {
const res = keys.map(k => {
return new Proxy(
{
get(property) {
return k.includes(property) ? props[property as any] : undefined;
},
has(property) {
return k.includes(property) && property in props;
},
keys() {
return k.filter(property => property in props);
}
},
propTraps
);
});
res.push(
new Proxy(
{
get(property) {
return blocked.has(property) ? undefined : props[property as any];
},
has(property) {
return blocked.has(property) ? false : property in props;
},
keys() {
return Object.keys(props).filter(k => !blocked.has(k));
}
},
propTraps
)
);
return res as SplitProps<T, K>;
}
const descriptors = Object.getOwnPropertyDescriptors(props);
keys.push(Object.keys(descriptors).filter(k => !blocked.has(k as keyof T)) as (keyof T)[]);
return keys.map(k => {
const clone = {};
for (let i = 0; i < k.length; i++) {
const key = k[i];
if (!(key in props)) continue; // skip defining keys that don't exist
Object.defineProperty(
clone,
key,
descriptors[key]
? descriptors[key]
: {
get() {
return props[key];
},
set() {
return true;
},
enumerable: true
}
);
}
return clone;
}) as SplitProps<T, K>;
}

export function mergeProps<T extends unknown[]>(...sources: T): MergeProps<T> {
let proxy = false;
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
proxy = proxy || (!!s && $PROXY in (s as object));
sources[i] =
typeof s === "function" ? ((proxy = true), createMemo(s as EffectFunction<unknown>)) : s;
}
if (proxy) {
return new Proxy(
{
get(property: string | number | symbol) {
for (let i = sources.length - 1; i >= 0; i--) {
const v = resolveSource(sources[i])[property];
if (v !== undefined) return v;
}
},
has(property: string | number | symbol) {
for (let i = sources.length - 1; i >= 0; i--) {
if (property in resolveSource(sources[i])) return true;
}
return false;
},
keys() {
const keys = [];
for (let i = 0; i < sources.length; i++)
keys.push(...Object.keys(resolveSource(sources[i])));
return [...new Set(keys)];
}
},
propTraps
) as unknown as MergeProps<T>;
}
const target = {} as MergeProps<T>;
for (let i = sources.length - 1; i >= 0; i--) {
if (sources[i]) {
const descriptors = Object.getOwnPropertyDescriptors(sources[i]);
for (const key in descriptors) {
if (key in target) continue;
Object.defineProperty(target, key, {
enumerable: true,
get() {
for (let i = sources.length - 1; i >= 0; i--) {
const v = ((sources[i] as any) || {})[key];
if (v !== undefined) return v;
}
}
});
}
}
}
return target;
}

0 comments on commit 4bc350b

Please sign in to comment.