Skip to content

Commit 30bf556

Browse files
committed
perf: add unicode-compliant naive, swar, and simd deserialization
1 parent 8a9fe5a commit 30bf556

17 files changed

Lines changed: 659 additions & 564 deletions

File tree

.github/workflows/benchmark.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ jobs:
4848
bun ./scripts/build-chart01.ts || true
4949
bun ./scripts/build-chart02.ts || true
5050
bun ./scripts/build-chart03.ts || true
51+
bun ./scripts/build-chart04.ts || true
5152
continue-on-error: true
5253

5354
- name: Commit Charts to docs branch

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ The following charts compare JSON-AS (both SWAR and SIMD variants) against JavaS
413413

414414
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/chart03.png" alt="Performance Chart 3">
415415

416+
<img src="https://raw.githubusercontent.com/JairusSW/json-as/refs/heads/docs/charts/chart04.png" alt="Performance Chart 3">
417+
416418
> Note: I have focused on extensively optimizing serialization. I used to have deserialization be highly unsafe and extremely fast, but I've since doubled down on safety for deserialization which has negatively affected performance. I will be optimizing soon.
417419
418420
### Performance Tuning

assembly/__tests__/lib/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1+
import { JSON } from "../..";
2+
13
let currentDescription: string = "";
4+
let currentId: u32 = 0;
25
export function describe(description: string, routine: () => void): void {
36
currentDescription = description;
7+
currentId = 0;
48
routine();
59
}
610

711
export function it(description: string, routine: () => void): void {
812
currentDescription = description;
13+
currentId = 0;
914
routine();
1015
}
1116

1217
export function expect<T>(left: T): Expectation {
18+
currentId++;
1319
// @ts-ignore
1420
if (!isDefined(left.toString)) throw new Error("Expected left to have a toString method, but it does not.");
1521
// @ts-ignore
@@ -27,10 +33,10 @@ class Expectation {
2733
if (!isDefined(right.toString)) throw new Error("Expected right to have a toString method, but it does not.");
2834
// @ts-ignore
2935
if (this.left != (isNull(right) ? "null" : right.toString())) {
30-
console.log(" " + currentDescription + "\n");
36+
console.log(" " + currentDescription + "#" + currentId.toString() + "\n");
3137
// @ts-ignore
32-
console.log(" (expected) -> " + (isNull(right) ? "null" : right.toString()));
33-
console.log(" (received) -> " + this.left);
38+
console.log(" (expected) -> " + (isNull(right) ? "null" : JSON.stringify(right.toString())));
39+
console.log(" (received) -> " + JSON.stringify(this.left));
3440
unreachable();
3541
}
3642
}

assembly/__tests__/string.spec.ts

Lines changed: 74 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe("Should deserialize strings - Basic", () => {
145145
expect(JSON.parse<string>('"\\"st\\\\\\"ring\\\\\\" w\\\\\\"\\\\\\"ith quotes\\\\\\"\\""')).toBe('"st\\"ring\\" w\\"\\"ith quotes\\""');
146146
expect(JSON.parse<string>('"\\"string \\\\\\"with random spa\\\\nces and \\\\nnewlines\\\\n\\\\n\\\\n\\""')).toBe('"string \\"with random spa\\nces and \\nnewlines\\n\\n\\n"');
147147
expect(JSON.parse<string>('"\\"string with colon : comma , brace [ ] bracket { } and quote \\\\\\" and other quote \\\\\\\\\\"\\""')).toBe('"string with colon : comma , brace [ ] bracket { } and quote \\" and other quote \\\\""');
148-
expect(JSON.parse<string>('"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u000f\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f"')).toBe("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u000f\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f");
148+
expect(JSON.parse<string>('"a\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f"')).toBe("a\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f");
149149
expect(JSON.parse<string>('"abcdYZ12345890sdfw\\"vie91kfESDFOK12i9i12dsf./?"')).toBe("abcdYZ12345890sdfw\"vie91kfESDFOK12i9i12dsf./?");
150150
});
151151

@@ -159,79 +159,78 @@ describe("Should deserialize strings - Empty and whitespace", () => {
159159
expect(JSON.parse<string>('"\\r\\n"')).toBe("\r\n");
160160
});
161161

162-
// describe("Should deserialize strings - Special characters", () => {
163-
// expect(JSON.parse<string>('"\\"')).toBe('"');
164-
// expect(JSON.parse<string>('"\\\\"')).toBe("\\");
165-
// expect(JSON.parse<string>('"\\"\\\\"')).toBe('"\\');
166-
// expect(JSON.parse<string>('"\\\\\\"')).toBe('\\"');
167-
// expect(JSON.parse<string>('"\\/"')).toBe("/");
168-
// expect(JSON.parse<string>('"\\b"')).toBe("\b");
169-
// expect(JSON.parse<string>('"\\f"')).toBe("\f");
170-
// expect(JSON.parse<string>('"\\n"')).toBe("\n");
171-
// expect(JSON.parse<string>('"\\r"')).toBe("\r");
172-
// expect(JSON.parse<string>('"\\t"')).toBe("\t");
173-
// });
174-
175-
// describe("Should deserialize strings - Unicode escapes", () => {
176-
// expect(JSON.parse<string>('"\\u0000"')).toBe("\u0000");
177-
// expect(JSON.parse<string>('"\\u0001"')).toBe("\u0001");
178-
// expect(JSON.parse<string>('"\\u001f"')).toBe("\u001f");
179-
// expect(JSON.parse<string>('"\\u0041"')).toBe("A");
180-
// expect(JSON.parse<string>('"\\u0061"')).toBe("a");
181-
// expect(JSON.parse<string>('"\\u00e9"')).toBe("é");
182-
// expect(JSON.parse<string>('"\\u4e2d\\u6587"')).toBe("中文");
183-
// });
184-
185-
// describe("Should deserialize strings - Mixed escapes", () => {
186-
// expect(JSON.parse<string>('"abc\\"def"')).toBe('abc"def');
187-
// expect(JSON.parse<string>('"line1\\nline2"')).toBe("line1\nline2");
188-
// expect(JSON.parse<string>('"tab\\there"')).toBe("tab\there");
189-
// expect(JSON.parse<string>('"back\\\\slash"')).toBe("back\\slash");
190-
// expect(JSON.parse<string>('"\\"\\\\\\/\\b\\f\\n\\r\\t"')).toBe('"\\/\b\f\n\r\t');
191-
// });
192-
193-
// describe("Should deserialize strings - Unicode characters (non-escaped)", () => {
194-
// expect(JSON.parse<string>('"café"')).toBe("café");
195-
// expect(JSON.parse<string>('"hello 世界"')).toBe("hello 世界");
196-
// expect(JSON.parse<string>('"Здравствуйте"')).toBe("Здравствуйте");
197-
// expect(JSON.parse<string>('"مرحبا"')).toBe("مرحبا");
198-
// });
199-
200-
// // Surrogate tests commented out - deserialization doesn't handle surrogates yet
201-
// // describe("Should deserialize strings - Surrogates", () => {
202-
// // expect(JSON.parse<string>('"\\ud83d\\ude00"')).toBe("\uD83D\uDE00"); // 😀
203-
// // expect(JSON.parse<string>('"\\ud834\\udd1e"')).toBe("\uD834\uDD1E"); // Musical symbol
204-
// // expect(JSON.parse<string>('"\\ud800"')).toBe("\uD800"); // Unpaired high
205-
// // expect(JSON.parse<string>('"\\udc00"')).toBe("\uDC00"); // Unpaired low
206-
// // });
207-
208-
// describe("Should deserialize strings - Long strings", () => {
209-
// const long1 = '"' + "a".repeat(1000) + '"';
210-
// expect(JSON.parse<string>(long1)).toBe("a".repeat(1000));
162+
describe("Should deserialize strings - Special characters", () => {
163+
expect(JSON.parse<string>('"\\"')).toBe('"');
164+
expect(JSON.parse<string>('"\\\\"')).toBe("\\");
165+
expect(JSON.parse<string>('"\\"\\\\"')).toBe('"\\');
166+
expect(JSON.parse<string>('"\\\\\\"')).toBe('\\"');
167+
expect(JSON.parse<string>('"/"')).toBe("/");
168+
expect(JSON.parse<string>('"\\b"')).toBe("\b");
169+
expect(JSON.parse<string>('"\\f"')).toBe("\f");
170+
expect(JSON.parse<string>('"\\n"')).toBe("\n");
171+
expect(JSON.parse<string>('"\\r"')).toBe("\r");
172+
expect(JSON.parse<string>('"\\t"')).toBe("\t");
173+
});
174+
175+
describe("Should deserialize strings - Unicode escapes", () => {
176+
expect(JSON.parse<string>('"\\u0000"')).toBe("\u0000");
177+
expect(JSON.parse<string>('"\\u0001"')).toBe("\u0001");
178+
expect(JSON.parse<string>('"\\u001f"')).toBe("\u001f");
179+
expect(JSON.parse<string>('"\\u0041"')).toBe("A");
180+
expect(JSON.parse<string>('"\\u0061"')).toBe("a");
181+
expect(JSON.parse<string>('"\\u00e9"')).toBe("é");
182+
expect(JSON.parse<string>('"\\u4e2d\\u6587"')).toBe("中文");
183+
});
184+
185+
describe("Should deserialize strings - Mixed escapes", () => {
186+
expect(JSON.parse<string>('"abc\\"def"')).toBe('abc"def');
187+
expect(JSON.parse<string>('"line1\\nline2"')).toBe("line1\nline2");
188+
expect(JSON.parse<string>('"tab\\there"')).toBe("tab\there");
189+
expect(JSON.parse<string>('"back\\\\slash"')).toBe("back\\slash");
190+
expect(JSON.parse<string>('"\\"\\\\/\\b\\f\\n\\r\\t"')).toBe('"\\/\b\f\n\r\t');
191+
});
192+
193+
describe("Should deserialize strings - Unicode characters (non-escaped)", () => {
194+
expect(JSON.parse<string>('"café"')).toBe("café");
195+
expect(JSON.parse<string>('"hello 世界"')).toBe("hello 世界");
196+
expect(JSON.parse<string>('"Здравствуйте"')).toBe("Здравствуйте");
197+
expect(JSON.parse<string>('"مرحبا"')).toBe("مرحبا");
198+
});
199+
200+
describe("Should deserialize strings - Surrogates", () => {
201+
expect(JSON.parse<string>('"\\ud83d\\ude00"')).toBe("\uD83D\uDE00"); // 😀
202+
expect(JSON.parse<string>('"\\ud834\\udd1e"')).toBe("\uD834\uDD1E"); // Musical symbol
203+
expect(JSON.parse<string>('"\\ud800"')).toBe("\uD800"); // Unpaired high
204+
expect(JSON.parse<string>('"\\udc00"')).toBe("\uDC00"); // Unpaired low
205+
});
206+
207+
describe("Should deserialize strings - Long strings", () => {
208+
const long1 = '"' + "a".repeat(1000) + '"';
209+
expect(JSON.parse<string>(long1)).toBe("a".repeat(1000));
211210

212-
// const long2 = '"' + "abc\\ndef".repeat(100) + '"';
213-
// expect(JSON.parse<string>(long2)).toBe("abc\ndef".repeat(100));
214-
// });
215-
216-
// describe("Should deserialize strings - Roundtrip", () => {
217-
// const test_strings = [
218-
// "",
219-
// "hello",
220-
// "hello world",
221-
// 'quotes "inside" string',
222-
// "backslash \\ character",
223-
// "newline\ncharacter",
224-
// "tab\tcharacter",
225-
// "all together: \"\\\n\t",
226-
// "control chars: \u0000\u0001\u001f",
227-
// "unicode: café 世界",
228-
// "long string: " + "x".repeat(500)
229-
// ];
211+
const long2 = '"' + "abc\\ndef".repeat(100) + '"';
212+
expect(JSON.parse<string>(long2)).toBe("abc\ndef".repeat(100));
213+
});
214+
215+
describe("Should deserialize strings - Roundtrip", () => {
216+
const test_strings = [
217+
"",
218+
"hello",
219+
"hello world",
220+
'quotes "inside" string',
221+
"backslash \\ character",
222+
"newline\ncharacter",
223+
"tab\tcharacter",
224+
"all together: \"\\\n\t",
225+
"control chars: \u0000\u0001\u001f",
226+
"unicode: café 世界",
227+
"long string: " + "x".repeat(500)
228+
];
230229

231-
// for (let i = 0; i < test_strings.length; i++) {
232-
// const original = test_strings[i];
233-
// const serialized = JSON.stringify(original);
234-
// const deserialized = JSON.parse<string>(serialized);
235-
// expect(deserialized).toBe(original);
236-
// }
237-
// });
230+
for (let i = 0; i < test_strings.length; i++) {
231+
const original = test_strings[i];
232+
const serialized = JSON.stringify(original);
233+
const deserialized = JSON.parse<string>(serialized);
234+
expect(deserialized).toBe(original);
235+
}
236+
});

0 commit comments

Comments
 (0)