Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(serialize): optimized sorting for small objects and arrays #151

Open
wants to merge 14 commits into
base: perf/faster-compare
Choose a base branch
from
Open
52 changes: 44 additions & 8 deletions src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,57 @@ const Serializer = /*@__PURE__*/ (function () {
return typeA < typeB ? -1 : 1;
}

if (typeA === "string" || typeA === "number") {
if (typeA === "string" || typeA === "number" || typeA === "bigint") {
return a < b ? -1 : 1;
}

return this.serialize(a, true) < this.serialize(b, true) ? -1 : 1;
const serializedA = this.serialize(a);
const serializedB = this.serialize(b);
if (serializedA === serializedB) {
return 0;
}
return serializedA < serializedB ? -1 : 1;
}

sort<T>(array: T[], compare?: (a: T, b: T) => number): T[] {
const length = array.length;

if (length <= 1) {
return array;
}

if (length > 10) {
return array.sort(compare);
}

// Use insertion sort for small arrays (faster)
for (let i = 1; i < length; i++) {
const current = array[i];
let j = i;
// Code style: intentional for better performance
if (compare !== undefined) {
while (j > 0 && compare(current, array[j - 1]) === -1) {
array[j] = array[--j];
}
array[j] = current;
continue;
}
while (j > 0 && current < array[j - 1]) {
array[j] = array[--j];
}
array[j] = current;
}
return array;
}

serialize(value: any, noQuotes?: boolean): string {
serialize(value: any): string {
if (value === null) {
return "null";
}

switch (typeof value) {
case "string": {
return noQuotes ? value : `'${value}'`;
return `'${value}'`;
}
case "bigint": {
return `${value}n`;
Expand Down Expand Up @@ -108,7 +144,7 @@ const Serializer = /*@__PURE__*/ (function () {
);
}

const keys = Object.keys(object).sort();
const keys = this.sort(Object.keys(object));
let content = `${objName}{`;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
Expand All @@ -133,13 +169,13 @@ const Serializer = /*@__PURE__*/ (function () {
}

serializeObjectEntries(type: string, entries: Iterable<[any, any]>) {
const sortedEntries = Array.from(entries).sort((a, b) =>
const sortedEntries = this.sort(Array.from(entries), (a, b) =>
this.compare(a[0], b[0]),
);
let content = `${type}{`;
for (let i = 0; i < sortedEntries.length; i++) {
const [key, value] = sortedEntries[i];
content += `${this.serialize(key, true)}:${this.serialize(value)}`;
content += `${typeof key === "string" ? key : this.serialize(key)}:${this.serialize(value)}`;
if (i < sortedEntries.length - 1) {
content += ",";
}
Expand Down Expand Up @@ -193,7 +229,7 @@ const Serializer = /*@__PURE__*/ (function () {
}

$Set(set: Set<any>) {
return `Set${this.$Array(Array.from(set).sort((a, b) => this.compare(a, b)))}`;
return `Set${this.$Array(this.sort(Array.from(set), (a, b) => this.compare(a, b)))}`;
}

$Map(map: Map<any, any>) {
Expand Down
8 changes: 4 additions & 4 deletions test/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe("bundle size", () => {
`;
const { bytes, gzipSize } = await getBundleSize(code);
// console.log({ bytes, gzipSize });
expect(bytes).toBeLessThanOrEqual(2800); // <2.8kb
expect(gzipSize).toBeLessThanOrEqual(1200); // <1.2kb
expect(bytes).toBeLessThanOrEqual(3000); // <3.0kb
expect(gzipSize).toBeLessThanOrEqual(1300); // <1.3kb
});

it("hash", async () => {
Expand All @@ -33,8 +33,8 @@ describe("bundle size", () => {
`;
const { bytes, gzipSize } = await getBundleSize(code);
// console.log({ bytes, gzipSize });
expect(bytes).toBeLessThanOrEqual(3100); // <3.1kb
expect(gzipSize).toBeLessThanOrEqual(1300); // <1.3kb
expect(bytes).toBeLessThanOrEqual(3300); // <3.3kb
expect(gzipSize).toBeLessThanOrEqual(1400); // <1.4kb
});
});

Expand Down
47 changes: 45 additions & 2 deletions test/serialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,55 @@ describe("serialize", () => {
expect(serialize(new Set([1, 2, 3]))).toMatchInlineSnapshot(
`"Set[1,2,3]"`,
);
expect(serialize(new Set([2, 3, 1]))).toMatchInlineSnapshot(
`"Set[1,2,3]"`,

// Less than 10 elements
expect(
serialize(new Set([64, 38, 27, 81, 93, 29, 70, 45])),
).toMatchInlineSnapshot(`"Set[27,29,38,45,64,70,81,93]"`);

// More than 10 elements
expect(
serialize(
new Set([
64, 38, 27, 81, 93, 29, 70, 45, 77, 5, 16, 73, 89, 55, 90, 22, 7,
94, 60, 48, 33, 58, 23, 50, 65, 47, 11, 1, 83, 34, 85, 88, 24, 12,
74, 79, 82, 95, 17, 51, 39, 3, 62, 46, 37, 20, 69, 18, 56, 78, 86,
28, 44, 92, 8, 49, 15, 68, 35, 97, 59, 43, 2, 75, 41, 100, 52, 87,
21, 99, 91, 67, 42, 32, 61, 66, 80, 40, 25, 36, 6, 96, 72, 54, 84,
71, 10, 76, 53, 30, 57, 98, 4, 13, 31, 19, 9, 26, 63, 14,
]),
),
).toMatchInlineSnapshot(
`"Set[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100]"`,
);

expect(serialize(new Set([{ b: 1 }, { a: 1 }]))).toMatchInlineSnapshot(
`"Set[{a:1},{b:1}]"`,
);

expect(
serialize(
new Set([
"a",
16,
Symbol("c"),
2n,
undefined,
Symbol("a"),
{ c: 2 },
{ b: 3 },
"c",
null,
1n,
Symbol("b"),
{ d: 1 },
{ a: 4 },
64,
]),
),
).toMatchInlineSnapshot(
`"Set[1n,2n,16,64,null,{a:4},{b:3},{c:2},{d:1},'a','c',Symbol(a),Symbol(b),Symbol(c),undefined]"`,
);
});

it("map", () => {
Expand Down