Comparing version
@@ -8,2 +8,3 @@ import { JSON } from ".."; | ||
const blackBoxArea = memory.data(64); | ||
expect(JSON.stringify(v1)).toBe(v2); | ||
@@ -14,5 +15,6 @@ | ||
() => { | ||
inline.always(JSON.stringify(v1)); | ||
blackbox(inline.always(JSON.stringify(blackbox(v1)))); | ||
}, | ||
64_000_00, | ||
24_000_00, | ||
v1.length << 1, | ||
); | ||
@@ -23,5 +25,11 @@ | ||
() => { | ||
inline.always(JSON.parse<string>(v2)); | ||
blackbox(inline.always(JSON.parse<string>(blackbox(v2)))); | ||
}, | ||
64_000_00, | ||
24_000_00, | ||
v2.length << 1, | ||
); | ||
function blackbox<T>(value: T): T { | ||
store<T>(blackBoxArea, value); | ||
return load<T>(blackBoxArea); | ||
} |
@@ -1,3 +0,4 @@ | ||
export function bench(description: string, routine: () => void, ops: u64 = 1_000_000): void { | ||
export function bench(description: string, routine: () => void, ops: u64 = 1_000_000, bytesPerOp: u64 = 0): void { | ||
console.log(" - Benchmarking " + description); | ||
let warmup = ops / 10; | ||
@@ -7,13 +8,24 @@ while (--warmup) { | ||
} | ||
const start = Date.now(); | ||
const start = performance.now(); | ||
let count = ops; | ||
while (count != 0) { | ||
while (count--) { | ||
routine(); | ||
count--; | ||
} | ||
const elapsed = Date.now() - start; | ||
let opsPerSecond = (ops * 1000) / elapsed; | ||
const end = performance.now(); | ||
const elapsed = Math.max(1, end - start); | ||
console.log(` Completed benchmark in ${formatNumber(elapsed)}ms at ${formatNumber(opsPerSecond)} ops/s\n`); | ||
const opsPerSecond = f64(ops * 1000) / elapsed; | ||
let log = ` Completed benchmark in ${formatNumber(u64(Math.round(elapsed)))}ms at ${formatNumber(u64(Math.round(opsPerSecond)))} ops/s`; | ||
if (bytesPerOp > 0) { | ||
const totalBytes = bytesPerOp * ops; | ||
const mbPerSec = f64(totalBytes) / (elapsed / 1000) / (1000 * 1000); | ||
log += ` @ ${formatNumber(u64(Math.round(mbPerSec)))}MB/s`; | ||
} | ||
console.log(log + "\n"); | ||
} | ||
@@ -20,0 +32,0 @@ |
@@ -30,2 +30,4 @@ /// <reference path="./index.d.ts" /> | ||
import { deserializeRaw } from "./deserialize/simple/raw"; | ||
import { serializeString_SIMD } from "./serialize/simd/string"; | ||
// import { deserializeString_SIMD } from "./deserialize/simd/string"; | ||
@@ -116,7 +118,7 @@ /** | ||
// } | ||
// if (ASC_FEATURE_SIMD) { | ||
// serializeString_SIMD(data as string); | ||
// } else { | ||
serializeString(data as string); | ||
// } | ||
if (ASC_FEATURE_SIMD) { | ||
serializeString_SIMD(data as string); | ||
} else { | ||
serializeString(data as string); | ||
} | ||
return bs.out<string>(); | ||
@@ -184,3 +186,3 @@ // @ts-ignore: Supplied by transform | ||
// if (ASC_FEATURE_SIMD) { | ||
// // @ts-ignore | ||
// // @ts-ignore | ||
// return changetype<string>(deserializeString_SIMD(dataPtr, dataPtr + dataSize, __new(dataSize - 4, idof<string>()))); | ||
@@ -432,3 +434,3 @@ // } else { | ||
constructor() {} | ||
constructor() { } | ||
@@ -549,3 +551,7 @@ // @ts-ignore: decorator | ||
} else if (isString<nonnull<T>>()) { | ||
serializeString(src as string); | ||
if (ASC_FEATURE_SIMD) { | ||
serializeString_SIMD(src as string); | ||
} else { | ||
serializeString(src as string); | ||
} | ||
// @ts-ignore: Supplied by transform | ||
@@ -722,8 +728,8 @@ } else if (isDefined(src.__SERIALIZE_CUSTOM)) { | ||
// } | ||
// if (ASC_FEATURE_SIMD) { | ||
// serializeString_SIMD(data as string); | ||
// } else { | ||
bs.saveState(); | ||
serializeString(data as string); | ||
// } | ||
if (ASC_FEATURE_SIMD) { | ||
serializeString_SIMD(data as string); | ||
} else { | ||
bs.saveState(); | ||
serializeString(data as string); | ||
} | ||
return bs.cpyOut<string>(); | ||
@@ -730,0 +736,0 @@ // @ts-ignore: Supplied by transform |
@@ -6,2 +6,4 @@ import { bs } from "../../../lib/as-bs"; | ||
const U00_MARKER = 13511005048209500; | ||
/** | ||
@@ -17,3 +19,2 @@ * Serializes strings into their JSON counterparts using SIMD operations | ||
const SPLAT_32 = i16x8.splat(32); /* [ESC] */ | ||
const SPLAT_0 = i16x8.splat(0); /* 0 */ | ||
@@ -25,3 +26,3 @@ const srcSize = bytes(src); | ||
bs.proposeSize(srcSize + 4); | ||
bs.proposeSize(srcSize + 40); | ||
@@ -33,2 +34,3 @@ store<u8>(changetype<usize>(bs.offset), 34); /* " */ | ||
const block = v128.load(srcStart); | ||
v128.store(bs.offset, block); | ||
@@ -45,12 +47,10 @@ | ||
const lane_index = ctz(mask) << 1; | ||
const dst_offset = bs.offset + lane_index; | ||
const src_offset = srcStart + lane_index; | ||
const code = load<u16>(src_offset) << 2; | ||
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + code); | ||
mask &= mask - 1; | ||
if ((escaped & 0xffff) != BACK_SLASH) { | ||
bs.growSize(10); | ||
store<u64>(dst_offset, 13511005048209500); | ||
const dst_offset = bs.offset + lane_index; | ||
store<u64>(dst_offset, U00_MARKER); | ||
store<u32>(dst_offset, escaped, 8); | ||
@@ -61,2 +61,3 @@ v128.store(dst_offset, v128.load(src_offset, 2), 12); | ||
bs.growSize(2); | ||
const dst_offset = bs.offset + lane_index; | ||
store<u32>(dst_offset, escaped); | ||
@@ -72,97 +73,9 @@ v128.store(dst_offset, v128.load(src_offset, 2), 4); | ||
const rem = srcEnd - srcStart; | ||
if (rem & 8) { | ||
const block = v128.load64_zero(srcStart); | ||
v128.store64_lane(bs.offset, block, 0); | ||
const backslash_indices = i16x8.eq(block, SPLAT_92); | ||
const quote_indices = i16x8.eq(block, SPLAT_34); | ||
const escape_indices = i16x8.lt_u(block, SPLAT_32); | ||
const zero_indices = i16x8.eq(block, SPLAT_0); | ||
const sieve = v128.and(v128.or(v128.or(backslash_indices, quote_indices), escape_indices), v128.not(zero_indices)); | ||
let mask = i16x8.bitmask(sieve); | ||
while (mask != 0) { | ||
let lane_index = ctz(mask) << 1; | ||
const dst_offset = bs.offset + lane_index; | ||
const src_offset = srcStart + lane_index; | ||
const code = load<u16>(src_offset) << 2; | ||
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + code); | ||
mask &= mask - 1; | ||
if ((escaped & 0xffff) != BACK_SLASH) { | ||
bs.growSize(10); | ||
store<u64>(dst_offset, 13511005048209500); | ||
store<u32>(dst_offset, escaped, 8); | ||
while (lane_index < 6) { | ||
store<u8>(bs.offset + lane_index, load<u8>(srcStart + lane_index, 2), 12); | ||
lane_index += 2; | ||
} | ||
bs.offset += 10; | ||
} else { | ||
bs.growSize(2); | ||
store<u32>(dst_offset, escaped); | ||
while (lane_index < 6) { | ||
store<u8>(bs.offset + lane_index, load<u8>(srcStart + lane_index, 2), 4); | ||
lane_index += 2; | ||
} | ||
bs.offset += 2; | ||
} | ||
} | ||
bs.offset += 8; | ||
srcStart += 8; | ||
} | ||
if (rem & 4) { | ||
const block = load<u32>(srcStart); | ||
const codeA = block & 0xffff; | ||
const codeB = (block >> 16) & 0xffff; | ||
if (codeA == 92 || codeA == 34 || codeA < 32) { | ||
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + (codeA << 2)); | ||
if ((escaped & 0xffff) != BACK_SLASH) { | ||
bs.growSize(10); | ||
store<u64>(bs.offset, 13511005048209500); | ||
store<u32>(bs.offset, escaped, 8); | ||
bs.offset += 12; | ||
} else { | ||
bs.growSize(2); | ||
store<u32>(bs.offset, escaped); | ||
bs.offset += 4; | ||
} | ||
} else { | ||
store<u16>(bs.offset, codeA); | ||
bs.offset += 2; | ||
} | ||
if (codeB == 92 || codeB == 34 || codeB < 32) { | ||
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + (codeB << 2)); | ||
if ((escaped & 0xffff) != BACK_SLASH) { | ||
bs.growSize(10); | ||
store<u64>(bs.offset, 13511005048209500); | ||
store<u32>(bs.offset, escaped, 8); | ||
bs.offset += 12; | ||
} else { | ||
bs.growSize(2); | ||
store<u32>(bs.offset, escaped); | ||
bs.offset += 4; | ||
} | ||
} else { | ||
store<u16>(bs.offset, codeB); | ||
bs.offset += 2; | ||
} | ||
srcStart += 4; | ||
} | ||
if (rem & 2) { | ||
while (srcStart <= srcEnd - 2) { | ||
const code = load<u16>(srcStart); | ||
if (code == 92 || code == 34 || code < 32) { | ||
const escaped = load<u32>(SERIALIZE_ESCAPE_TABLE + (code << 2)); | ||
if ((escaped & 0xffff) != BACK_SLASH) { | ||
bs.growSize(10); | ||
store<u64>(bs.offset, 13511005048209500); | ||
store<u64>(bs.offset, U00_MARKER); | ||
store<u32>(bs.offset, escaped, 8); | ||
@@ -179,2 +92,3 @@ bs.offset += 12; | ||
} | ||
srcStart += 2; | ||
} | ||
@@ -181,0 +95,0 @@ |
import { JSON } from "../assembly/index"; | ||
import { bs } from "../lib/as-bs"; | ||
@json | ||
@@ -27,2 +28,3 @@ class Vec3 { | ||
@inline | ||
@@ -208,2 +210,3 @@ __INITIALIZE(): this { | ||
@json | ||
@@ -217,2 +220,3 @@ class Player { | ||
@omitif((self: this): boolean => self.age < 18) | ||
@@ -266,2 +270,3 @@ age!: i32; | ||
@inline | ||
@@ -268,0 +273,0 @@ __INITIALIZE(): this { |
@@ -9,5 +9,6 @@ import { bench } from "./lib/bench.js"; | ||
() => { | ||
JSON.stringify(v1); | ||
blackbox(JSON.stringify(blackbox(v1))); | ||
}, | ||
64_000_00, | ||
v1.length << 1, | ||
); | ||
@@ -18,5 +19,11 @@ | ||
() => { | ||
JSON.parse(v2); | ||
blackbox(JSON.parse(blackbox(v2))); | ||
}, | ||
64_000_00, | ||
v2.length << 1, | ||
); | ||
function blackbox<T>(value: T): T { | ||
(globalThis as any).__blackhole = value; | ||
return globalThis.__blackhole; | ||
} |
@@ -1,27 +0,42 @@ | ||
if (typeof console === "undefined") { | ||
console = { | ||
log: print, | ||
error: print, | ||
warn: print, | ||
}; | ||
} | ||
export function bench(description: string, routine: () => void, ops: number = 1_000_000, bytesPerOp: number = 0): void { | ||
console.log(" - Benchmarking " + description); | ||
export function bench(description: string, routine: () => void, ops: number = 1_000_000): void { | ||
console.log(" - Benchmarking " + description); | ||
let warmup = ops / 10; | ||
while (--warmup) { | ||
let warmup = Math.floor(ops / 10); | ||
while (warmup-- > 0) { | ||
routine(); | ||
} | ||
const start = Date.now(); | ||
const start = performance.now(); | ||
let count = ops; | ||
while (count !== 0) { | ||
while (count-- > 0) { | ||
routine(); | ||
count--; | ||
} | ||
const elapsed = Date.now() - start; | ||
const opsPerSecond = Math.round((ops * 1000) / elapsed); | ||
const format = new Intl.NumberFormat("en-US"); | ||
const end = performance.now(); | ||
const elapsed = Math.max(1, end - start); | ||
console.log(` Completed benchmark in ${format.format(elapsed)}ms at ${format.format(opsPerSecond)} ops/s\n`); | ||
const opsPerSecond = (ops * 1000) / elapsed; | ||
let log = ` Completed benchmark in ${formatNumber(Math.round(elapsed))}ms at ${formatNumber(Math.round(opsPerSecond))} ops/s`; | ||
if (bytesPerOp > 0) { | ||
const totalBytes = bytesPerOp * ops; | ||
const mbPerSec = totalBytes / (elapsed / 1000) / (1000 * 1000); | ||
log += ` @ ${formatNumber(Math.round(mbPerSec))}MB/s`; | ||
} | ||
console.log(log + "\n"); | ||
} | ||
function formatNumber(n: number): string { | ||
let str = n.toString(); | ||
let len = str.length; | ||
let result = ""; | ||
let commaOffset = len % 3; | ||
for (let i = 0; i < len; i++) { | ||
if (i > 0 && (i - commaOffset) % 3 === 0) result += ","; | ||
result += str.charAt(i); | ||
} | ||
return result; | ||
} |
@@ -13,2 +13,3 @@ const bytes = readbuffer("./build/" + arguments[0]); | ||
"Date.now": () => Date.now(), | ||
"performance.now": () => performance.now(), | ||
}, | ||
@@ -15,0 +16,0 @@ }); |
# Change Log | ||
## 2025-07-14 - 1.1.20 | ||
- feat: enable SIMD string serialization | ||
## 2025-06-30 - 1.1.19 | ||
@@ -4,0 +8,0 @@ |
{ | ||
"name": "json-as", | ||
"version": "1.1.19", | ||
"version": "1.1.20", | ||
"author": "Jairus Tanaka", | ||
@@ -12,6 +12,7 @@ "repository": { | ||
"@assemblyscript/wasi-shim": "^0.1.0", | ||
"@types/node": "^22.15.34", | ||
"@types/node": "^24.0.8", | ||
"assemblyscript": "^0.28.2", | ||
"assemblyscript-prettier": "^3.0.1", | ||
"prettier": "^3.6.2", | ||
"tinybench": "^4.0.1", | ||
"tsx": "^4.20.3", | ||
@@ -18,0 +19,0 @@ "typescript": "^5.8.3" |
@@ -9,3 +9,3 @@ <h6 align="center"> | ||
</span> | ||
AssemblyScript - v1.1.19 | ||
AssemblyScript - v1.1.20 | ||
</pre> | ||
@@ -395,3 +395,3 @@ </h6> | ||
| Vector3 Object | 38 bytes | 26,611,226 ops/s | 32,160,804 ops/s | 1,357 MB/s | 1,348 MB/s | | ||
| Alphabet String | 104 bytes | 13,617,021 ops/s | 18,390,804 ops/s | 1,416 MB/s | 1,986 MB/s | | ||
| Alphabet String | 104 bytes | 16,916,886 ops/s | 18,390,804 ops/s | 1,759 MB/s | 1,986 MB/s | | ||
| Small Object | 88 bytes | 24,242,424 ops/s | 12,307,692 ops/s | 2,133 MB/s | 1,083 MB/s | | ||
@@ -406,3 +406,3 @@ | Medium Object | 494 bytes | 4,060,913 ops/s | 1,396,160 ops/s | 2,006 MB/s | 689.7 MB/s | | ||
| Vector3 Object | 38 bytes | 8,791,209 ops/s | 5,369,12 ops/s | 357.4 MB/s | 204.3 MB/s | | ||
| Alphabet String | 104 bytes | 13,793,103 ops/s | 14,746,544 ops/s | 1,416 MB/s | 1,592 MB/s | | ||
| Alphabet String | 104 bytes | 12,830,228 ops/s | 12,140,296 ops/s | 1,334 MB/s | 1,311 MB/s | | ||
| Small Object | 88 bytes | 8,376,963 ops/s | 4,968,944 ops/s | 737.1 MB/s | 437.2 MB/s | | ||
@@ -409,0 +409,0 @@ | Medium Object | 494 bytes | 2,395,210 ops/s | 1,381,693 ops/s | 1,183 MB/s | 682.5 MB/s | |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
695451
-0.7%8
14.29%12477
-0.83%