From d04b86d34ff229b2a833635c3df2db23991413dc Mon Sep 17 00:00:00 2001 From: robobun Date: Sun, 28 Dec 2025 18:01:07 -0800 Subject: [PATCH] perf: use jsonStringifyFast for faster JSON serialization (#25733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Apply the same optimization technique from PR #25717 (Response.json) to other APIs that use JSON.stringify internally: - **IPC message serialization** (`ipc.zig`) - used for inter-process communication - **console.log with %j format** (`ConsoleObject.zig`) - commonly used for debugging - **PostgreSQL JSON/JSONB types** (`PostgresRequest.zig`) - database operations - **MySQL JSON type** (`MySQLTypes.zig`) - database operations - **Jest %j/%o format specifiers** (`jest.zig`) - test output formatting - **Transpiler tsconfig/macros** (`JSTranspiler.zig`) - build configuration ### Root Cause When calling `JSONStringify(globalObject, value, 0)`, the space parameter `0` becomes `jsNumber(0)`, which is NOT `undefined`. This causes JSC's FastStringifier (SIMD-optimized) to bail out: ```cpp // In WebKit's JSONObject.cpp FastStringifier::stringify() if (!space.isUndefined()) { logOutcome("space"_s); return { }; // Bail out to slow path } ``` Using `jsonStringifyFast` which passes `jsUndefined()` triggers the fast path. ### Expected Performance Improvement Based on PR #25717 results, these changes should provide ~3x speedup for JSON serialization in the affected APIs. ## Test plan - [x] Debug build compiles successfully - [x] Basic functionality verified (IPC, console.log %j, Response.json) - [x] Existing tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot Co-authored-by: Claude --- src/bun.js/ConsoleObject.zig | 4 ++-- src/bun.js/api/JSTranspiler.zig | 6 ++++-- src/bun.js/ipc.zig | 4 +++- src/bun.js/test/jest.zig | 3 ++- src/sql/mysql/MySQLTypes.zig | 3 ++- src/sql/postgres/PostgresRequest.zig | 3 ++- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index f9d540ed11..491689ecc4 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -1629,11 +1629,11 @@ pub const Formatter = struct { }, .j => { - // JSON.stringify the value + // JSON.stringify the value using FastStringifier for SIMD optimization var str = bun.String.empty; defer str.deref(); - try next_value.jsonStringify(global, 0, &str); + try next_value.jsonStringifyFast(global, &str); this.addForNewLine(str.length()); writer.print("{f}", .{str}); }, diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 86814d6dee..2524468d1a 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -165,7 +165,8 @@ pub const Config = struct { } if (!kind.isStringLike()) { - try tsconfig.jsonStringify(globalThis, 0, &out); + // Use jsonStringifyFast for SIMD-optimized serialization + try tsconfig.jsonStringifyFast(globalThis, &out); } else { out = try tsconfig.toBunString(globalThis); } @@ -204,7 +205,8 @@ pub const Config = struct { defer out.deref(); // TODO: write a converter between JSC types and Bun AST types if (is_object) { - try macros.jsonStringify(globalThis, 0, &out); + // Use jsonStringifyFast for SIMD-optimized serialization + try macros.jsonStringifyFast(globalThis, &out); } else { out = try macros.toBunString(globalThis); } diff --git a/src/bun.js/ipc.zig b/src/bun.js/ipc.zig index 5ac9975619..fa00ec05a5 100644 --- a/src/bun.js/ipc.zig +++ b/src/bun.js/ipc.zig @@ -229,7 +229,9 @@ const json = struct { pub fn serialize(writer: *bun.io.StreamBuffer, global: *jsc.JSGlobalObject, value: JSValue, is_internal: IsInternal) !usize { var out: bun.String = undefined; - try value.jsonStringify(global, 0, &out); + // Use jsonStringifyFast which passes undefined for the space parameter, + // triggering JSC's SIMD-optimized FastStringifier code path. + try value.jsonStringifyFast(global, &out); defer out.deref(); if (out.tag == .Dead) return IPCSerializationError.SerializationFailed; diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index cb913c812d..1acacb9994 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -430,7 +430,8 @@ pub fn formatLabel(globalThis: *JSGlobalObject, label: string, function_args: [] 'j', 'o' => { var str = bun.String.empty; defer str.deref(); - try current_arg.jsonStringify(globalThis, 0, &str); + // Use jsonStringifyFast for SIMD-optimized serialization + try current_arg.jsonStringifyFast(globalThis, &str); const owned_slice = bun.handleOom(str.toOwnedSlice(allocator)); defer allocator.free(owned_slice); bun.handleOom(list.appendSlice(owned_slice)); diff --git a/src/sql/mysql/MySQLTypes.zig b/src/sql/mysql/MySQLTypes.zig index 0280718592..a4efe2fdba 100644 --- a/src/sql/mysql/MySQLTypes.zig +++ b/src/sql/mysql/MySQLTypes.zig @@ -486,7 +486,8 @@ pub const Value = union(enum) { .MYSQL_TYPE_JSON => { var str: bun.String = bun.String.empty; - try value.jsonStringify(globalObject, 0, &str); + // Use jsonStringifyFast for SIMD-optimized serialization + try value.jsonStringifyFast(globalObject, &str); defer str.deref(); return Value{ .string = str.toUTF8(bun.default_allocator) }; }, diff --git a/src/sql/postgres/PostgresRequest.zig b/src/sql/postgres/PostgresRequest.zig index 775ab536c9..c06bc28c19 100644 --- a/src/sql/postgres/PostgresRequest.zig +++ b/src/sql/postgres/PostgresRequest.zig @@ -101,7 +101,8 @@ pub fn writeBind( .jsonb, .json => { var str = bun.String.empty; defer str.deref(); - try value.jsonStringify(globalObject, 0, &str); + // Use jsonStringifyFast for SIMD-optimized serialization + try value.jsonStringifyFast(globalObject, &str); const slice = str.toUTF8WithoutRef(bun.default_allocator); defer slice.deinit(); const l = try writer.length();