perf(client-generator-ts,client-generator-js): read the search queue by index instead of shift() - #30199
perf(client-generator-ts,client-generator-js): read the search queue by index instead of shift()#30199lpbonomi wants to merge 1 commit into
Conversation
…by index instead of shift()
`GenericArgsInfo.typeNeedsGenericModelArg` runs a breadth-first search over
the input types reachable from a type and consumed its queue with
`Array.prototype.shift()`. On a schema with a few hundred models the search
from a create input visits most of the nested create inputs, and the queue
grows to more than a hundred thousand items; V8's `shift()` is linear in the
array length at that size, so the search is quadratic in its own queue.
A CPU profile of `prisma generate` on a 316-model schema put 241 s of 272 s
in that loop.
Reading the queue through an index keeps the same order and the same visits.
Generator time on the released 7.9.1 CLI with this change applied to its
bundle, generated output byte-identical:
316-model schema (37k input types) 165 s -> 6 s
odoo.prisma fixture (168 models) 73 s -> 3.6 s
synthetic, N models x 4 relations each:
100 models x 4 relations 2.1 s -> 0.9 s
200 models x 4 relations 8.0 s -> 2.0 s
300 models x 4 relations 18.4 s -> 3.1 s
500 models x 4 relations 55.0 s -> 5.6 s
300 models x 8 relations 113.7 s -> 6.3 s
Both generator packages carry the same file; both get the same change. The
existing tests and the generation snapshot tests pass unchanged.
Signed-off-by: luisopine <luis@tryopine.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughBoth client generators now process the ChangesGeneric model traversal
Estimated code review effort: 1 (Trivial) | ~2 minutes Merge Risk: ⚪ Minimal · up to This change replaces queue shifting with indexed reads in the client generators while preserving traversal behavior and generated output; no actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Docstring CoverageExplanation No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2 files. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Problem
prisma generatespends almost all of its time inGenericArgsInfo.typeNeedsGenericModelArgon schemas with a few hundred models. A CPU profile (node --cpu-prof) ofprisma generate --generator clienton a 316-model schema, Prisma 7.9.1, on Machine A below: 241 s of 272 s in that function; the same schema takes 165 s there without the profiler and 139 s on 7.10.0. #29308 reports the same shape (530 models, 235 s on 7.3.0).The function runs a breadth-first search over the input types reachable from a type and consumes its queue with
Array.prototype.shift(). The search from a create input visits most of the nested create inputs of the schema, so the queue grows to more than a hundred thousand items, and at that size V8'sshift()is linear in the array length: the search becomes quadratic in its own queue. The traversal itself is already linear in the number of types and references; the negative result caches every visited type, and only a few dozen types of a real DMMF are positive because the*WhereInputfamily carriesmeta.source.Change
Read the queue through an index instead of
shift(). Same order, same visits, same cache behaviour; one line in each of the two generator packages, which carry the same file.Correctness
FieldRefsites): generated client byte-identical for all 326 files (diff -ragainst the released generator's output).packages/internals/src/__tests__/__fixtures__/odoo.prisma(168 models) and synthetic schemas at 100, 200, 300 and 500 models: byte-identical at every size.GenericsArgsInfo.test.tstests and both packages' suites pass unchanged (see below).Performance
Generator time reported by the CLI ("Generated Prisma Client in ..."), measured by applying this same one-line change to the released
prisma@7.9.1bundle and running it against the unmodified bundle on the same schema and machine. The synthetic schemas come from the script below (N models, each with K relations to random other models, seeded);odoo.prismaispackages/internals/src/__tests__/__fixtures__/odoo.prisma.Machine A — AWS r7i VM: Intel Xeon Platinum 8488C, 8 vCPU, 61 GB, Ubuntu 24.04 (glibc 2.39), Node 24.18.
odoo.prismafixtureMachine B — MacBook Pro 16-inch 2023, Apple M3 Pro (6 performance + 6 efficiency cores), 36 GB, macOS 26.4.1.
Node 24.18 (V8 13.6), median of 3 runs:
odoo.prismafixtureNode 22.22, single run:
odoo.prismafixtureMachine dependence. The stock cost is the per-element cost of
shift()once the queue passes the 128 KB large-object threshold, which V8 no longer left-trims: a memmove with an 8-byte overlap plus per-slot bookkeeping. That constant depends on the V8 version and on the CPU and libc. On Machine B, Node 24 (V8 13.6) is 2–4× faster than Node 22 on the same shapes; on Machine A, Node 22, 24 and 26 are within 15% of each other because its CPU/libc pair handles the 8-byte-overlap memmove at 2.9 GB/s against 51 GB/s for any other offset (rep movsbsmall-overlap path; withGLIBC_TUNABLES=glibc.cpu.x86_rep_movsb_threshold=1000000000its 316-model generation takes 87 s instead of 165 s). A 4-vCPU Blacksmith CI runner (Node 26.8.1) generates the same private schema in 7.9 s stock. The change removes theshift()cost on every machine and version; the patched times are the rest of the generator, flat at 2–6 s across all of the above.Peak RSS is within a few percent in every case but the densest synthetic schema on Machine A, where the default V8 heap grows 23% larger (the consumed part of the queue stays referenced until the search returns). That is GC slack, not retained data: with
--max-old-space-size=1400the same run finishes in the same time at 1837 MB, the stock figure, and it still completes under a 1000 MB cap.Reproduction: synthetic schema generator
With a
prisma.config.tsnext to it (datasource: { url: "postgresql://" }), runprisma generatewith the released CLI and with a CLI that has this change, thendiff -rthe twooutdirectories and compare the "Generated Prisma Client in ..." lines. Theodoo.prismafixture in this repository reproduces the effect without a synthetic schema.Package suites
pnpm exec vitest runafterturbo run build --filter=@prisma/client:client-generator-js: 4 files, 20 tests, all pass (includes the end-to-end generation snapshot tests).client-generator-ts: 4 files, 49 of 50 pass. The one failure,workerd - issue prisma#28073, fails identically on the unmodifiedv7source in this environment: it needsquery_compiler_fast_bg.sqlite.wasmfrom a builtpackages/cli.GenericsArgsInfo.test.ts: 9 tests in each package, unchanged.Related: #29308.
🤖 Generated with Claude Code