XPerf
Guides

XPerf cross-language smell catalog

XPerf umbrella map: the same performance smell across kits. Rule IDs are per-kit (not shared). A YES / measured ratio on one runtime does not transfer to another — re-bench before porting advice.

KitLanguageLint IDs
zperfZigzp-alloc-hot–zp-alloc-print-hot
cperfCcp-alloc-hot–cp-struct-by-value
jsperfJS/TS (V8)jp-alloc-hot–jp-clone-per-event
jvmperfJava/Kotlin29 active jp-* slugs, jp-boxed-primitive-collection…jp-non-static-field-updater (see its docs/rule-ids.md; ≠ jsperf)
goperfGogp-make-hot–gp-time-now-hot
pyperfPythonpp-str-plus-hot–pp-open-hot
swiftperfSwiftsp-array-no-reserve, sp-print-hot, plus sp-dateformatter-hot / sp-json-decode-hot / sp-regex-hot / sp-file-read-hot / sp-clock-hot / sp-value-for-key-hot
arktsperfArkTSat-alloc-hot–at-json-hot
cppperfC++17cx-regex-ctor-hot, cx-endl-hot, cx-clock-hot, cx-ifstream-hot, cx-const-by-value, cx-vector-bool (string append and string-stream removed)
csharpperfC#cs-list-add-no-capacity–cs-field-by-name-hot
phpperfPHPph-string-dot-hot–ph-echo-hot
rubyperfRubyrb-string-plus-hot–rb-file-read-hot
rustperfRustrs-alloc-hot–rs-clock-hot (rs-map-new-hot restored, about 1.5×)
kotlinperfKotlinkt-list-add-hot–kt-println-hot (empty-add removed)
objcperfObjective-Coc-nsstring-append-hot / oc-nslog-hot (both off), plus oc-dateformatter-hot / oc-json-hot / oc-regex-hot / oc-file-read-hot / oc-clock-hot / oc-value-for-key-hot (array-add removed)
dartperfDartdt-list-add-empty–dt-growable-add-empty
luaperfLualu-table-insert-hot–lu-print-hot
beamperfElixir + Erlangbe-list-concat-reduce–be-enum-map-filter-chain
nimperfNimnm-seq-add-hot–nm-seq-no-capacity

Prefix overlap: jsperf and jvmperf both use the jp- prefix, with disjoint slug sets. For example jp-boxed-primitive-collection (~5.96×) exists only in jvmperf, and jp-alloc-hot (~19×, off under the app preset) only in jsperf. Always name the kit when citing a jp-* rule.


How to read

  1. Pick a smell → open each kit’s rule + packages/<kit>/evidence/<ID>.md before treating a finding as a speed claim.
  2. Evidence column cites ratios already in evidence-policy / JSONL / STATIC-VS-* — not invented here.
  3. Cells:
    • rule id + short note = shipped lint (or guide example)
    • n/a = runtime physics differ; do not force a mapping
    • pending = rule or stub exists; no honest wall-clock JSONL yet
    • guide = chapter / example only (no lint yet)
  4. Porting across languages must re-bench (roadmap-next.md §5).
  5. Fixture fire ≠ speed; deopt / PrintCompilation line counts ≠ escalate (rule-regression.md).

Catalog

SmellzperfcperfjsperfjvmperfEvidenceNotes
alloc-in-loop / arena / poolzp-alloc-hot ~1.4× GPA vs arena; zp-per-packet-alloc (hot-path, shares zp-alloc-hot); zp-create-destroy-hot ~11× create/destroy vs pool; zp-fixed-size-alloc-hot ~8.5× fixed alloc(u8,N) vs poolcp-alloc-hot ~10.8× malloc-loop vs arena (error)jp-alloc-hot ~19× / ~6.4× new/push vs once (off under app preset — alloc/GC, not deopt)jp-expensive-alloc-hot expensive factory in loop (hint; tiny-pool negative); jp-hot-heavy-alloc hot heavy alloc (pending ART); jp-hot-payload-copy ~15.8× frame buffer reuse (error)z: zp-alloc-hot, zp-create-destroy-hot, zp-fixed-size-alloc-hot JSONL; c: cp-alloc-hot; js: jp-alloc-hot; jvm: jp-expensive-alloc-hot, jp-hot-payload-copy (+ jp-hot-heavy-alloc stub)Same idea (hoist / reuse); Zig smp already cheap → prefer pool for fixed size. jvm jp-expensive-alloc-hot ≠ “always pool”.
buffer grow / capacity / builderzp-append-without-reserve ~2.1× append reserve; zp-map-put-without-ensure ~2.8× map ensure; zp-shift-on-read ~17× shift-on-read vs cursor (candidate error)cp-realloc-grow-one ~12.6× realloc+1 vs doubling (error)jp-string-plus-in-loop (removed 2026-10-23; push+join was not ~2×); V8 arrays grow differently; prealloc appears under jp-alloc-hot good fixturesjp-string-concat-hot ~14.2× string += vs builder (error); jp-unpresized-collection ~1.6× unpresized fill (hint PARTIAL); jp-baos-tobytearray-copy ~12.8× BAOS toByteArray vs writeTo (error)z: zp-append-without-reserve, zp-shift-on-read, zp-map-put-without-ensure; c: cp-realloc-grow-one; jvm: jp-string-concat-hot, jp-unpresized-collection, jp-baos-tobytearray-copyClosest cross-kit pair: cp-realloc-grow-one ↔ zp-append-without-reserve ↔ jvm jp-string-concat-hot. Not the same as JS holey.
hot IO / print / log / formatzp-print-hot ~6e4× print vs quiet (env I/O, not escalate); zp-unbuffered-io ~49× empty Writer vs 4KiB (candidate error)cp-printf-hot ~4.8× unbuf/buf hot; #if 0 ~1× → off (default app preset)jp-console-hot ~1328× console vs quiet (off app / hint engine — env)jp-format-hot ~17× String.format hot (error); jp-log-hot ~28× unguarded log concat (error); jp-hot-format-log hot format/log (pending ART)z: zp-print-hot, zp-unbuffered-io; c: cp-printf-hot; js: jp-console-hot; jvm: jp-format-hot, jp-log-hot (+ jp-hot-format-log stub)Prefer buffered Io / level guards. Do not escalate env-bound print alone.
shape / layout / SoA / densityzp-aos-single-field ~6.6× AoS→SoA; zp-packable-bools / related density stubs (pending); zp-large-struct-by-value by-value (inconclusive -Ofast); zp-bool-array ~30× bool[] vs bitsetguide SoA examples (p01_soa.c); no CP ruleguide §04 maps / IC / polymorphic (~12× in STATIC-VS-V8); no layout JPjp-many-boolean-fields (removed 2026-10-22; COH 16=16 no win); Kotlin layout smells elsewherez: zp-aos-single-field, zp-bool-array JSONL + pending stubs; extern padding removed (~1.2×); js: STATIC-VS-V8; jvm: jp-many-boolean-fieldsV8 hidden classes ≠ C padding ≠ Zig extern ≠ HotSpot object header.
holey / sparse elementsn/a — Zig slices ≠ V8 elements kindsn/a — C “sparse” is CSR/hash, different problemjp-delete-array-hole ~3.2× holey delete a[i] vs packed (error); STATIC case2 ~5.5×jp-android-sparse-array Android SparseArray (pending ART) — not V8 holeyjs: jp-delete-array-hole (+ STATIC-VS-V8); jvm: jp-android-sparse-array stub onlyDo not map jp-delete-array-hole → CP or ZP. Android Sparse* ≠ V8 holey.
delete / remove / tombstonezp-queue-ordered-remove ~466× orderedRemove vs ring (candidate error)n/ajp-delete-shape ~474× delete prop (off under app preset — G3 OK-idiom demote)n/a as V8/Zig remove; closest reuse smells are buffer/pooljs: jp-delete-shape; z: zp-queue-ordered-removeRelated cost of mid-structure delete, not the same smell (JS tombstone ≠ Zig memmove).
sync / lock / contested typesn/a (no ZP sync rule)n/an/ajp-legacy-synchronized-type (removed 2026-10-22; ~1.08× / HT negative)jvm: _removed/ tombstoneContended lock cost is workload-dependent; micro alone does not escalate.
boxed / value boxing / stream boxn/a (no managed boxing)n/an/a (numbers are values; objects under alloc jp-alloc-hot)jp-boxed-primitive-collection ~5.96× boxed collections (error); jp-boxed-stream ~149× boxed Stream (error); jp-value-class-boxing ~5× B/op value-class box (error)jvm: jp-boxed-primitive-collection, jp-boxed-stream, jp-value-class-boxing; STATIC-VS-HOTSPOTJVM-specific; jsperf has no boxing rule.
string / intern / concat familyn/a as JVM intern; Zig has zp-static-string-map StaticStringMap ~2.5×n/an/a as intern (jp-string-plus-in-loop removed 2026-10-23; alloc cost under jp-alloc-hot)jp-string-concat-hot concat (error); jp-string-intern-hot ~6.6× intern() in loop (error); jp-format-hot format (error)jvm: jp-string-concat-hot, jp-format-hot, jp-string-intern-hot; z: zp-static-string-mapOverlaps hot-IO/format row for jp-format-hot; listed here for string-table / builder family.
regex / parse control-flown/an/ajp-map-filter-chain (hint; 35000 rows ~4.6×–9.8×, 3-row filter+map ~1.3×)jp-regex-hot ~1.36× regex compile in loop (hint); jp-exception-control-flow ~64× exception-as-control (error)jvm: jp-regex-hot, jp-exception-control-flowWeak wall for regex → stays hint; exceptions-as-flow is strong.
collection chain / copy / lambda alloc (Kotlin)n/an/an/ajp-collection-chain ~265× eager chain (off); jp-lambda-to-non-inline-hot ~244× B/op non-inline lambda (off); jp-data-copy-hot ~208× copy in loop (error); jp-lazy-default-mode lazy mode (pending)jvm: jp-collection-chain, jp-lambda-to-non-inline-hot, jp-data-copy-hot (+ jp-lazy-default-mode stub)Kotlin-centric; B/op often clearer than ns/op.
enum / map specializationzp-static-string-map StaticStringMap ~2.5× (literal keys)n/an/ajp-enum-key-hash-collection ~2.1× EnumMap vs HashMap enum keys (error)z: zp-static-string-map; jvm: jp-enum-key-hash-collectionCompile-time / ordinal maps — similar idea, different APIs.
client / updater / factory recreaten/an/an/ajp-client-factory-recreate ~5e5× HttpClient new vs reuse (error); jp-non-static-field-updater create ~165× / steady inc ~1.0× (hint)jvm: jp-client-factory-recreate, jp-non-static-field-updaterFactory reuse; steady-state may be flat (jp-non-static-field-updater).
Android / ART device smellsn/an/an/ajp-android-sparse-array, jp-hot-heavy-alloc–jp-ui-text-append stubs only (pending ART/device): SparseArray, hot alloc, format/log, system call, Bitmap, Handler post, UI textjvm evidence stubs jp-android-sparse-array … jp-ui-text-appendNo box JSONL until ART/device harness.
correctness / static-only (not speed gates)zp-debug-allocator mode switch; zp-missing-free leak; zp-catch-unreachable-alloc catch unreachable; zp-oversized-enum-tag enum tag size — all N/A wall——jp-finalize finalize (pending design/correctness); vt-unsafe-threadlocal-pool guide-only VT/pool myth (not shipped)z: stubs zp-debug-allocator / zp-missing-free / zp-catch-unreachable-* / zp-oversized-enum-tag; jvm: jp-finalize + vt-unsafe-threadlocal-poolDo not invent speed JSONL for these.

Catalog rows: 15 smell families (z/c/js/jvm) + sections for go/py/swift/arkts/cpp/csharp/php/ruby/rust/kotlin/objc/dart/lua/beam/nim. All 19 kits corpus-ready (AGENT.md G3, 2026-10-22 Asia/Shanghai) — findings remain heuristics, not guaranteed speedups.

jvmperf policy snapshot: see packages/jvmperf/docs/evidence-policy.md for current error/hint/off/pending maps (slug IDs). Guide-only: vt-unsafe-threadlocal-pool (not shipped). Removed measured-useless: jp-legacy-synchronized-type, jp-many-boolean-fields.


Newer kits

Defaults are mostly off or hint. All kits below are corpus-ready under G3. Measured JSONL exists for most rules (see rule-regression.md); nimperf has none because G4 was blocked (no toolchain). Re-bench before escalating severity.

SmellgoperfpyperfswiftperfarktsperfNotes
alloc-in-loop / arena / poolgp-make-hot make/new in loop (off)n/a as make; closest pp-list-plus-rebind list growthn/a (ARC)at-alloc-hot object/new/push{ in loop (off)Same idea as cp-alloc-hot / js jp-alloc-hot; ratios in each kit's evidence-policy (swift: none, G4 blocked)
buffer grow / string buildergp-string-plus-hot string += (off); gp-append-nil-slice append on nil slice (hint)pp-str-plus-hot string += (off); pp-list-plus-rebind list + rebinding (hint)sp-array-no-reserve append without reserveCapacity (hint); string += removed (~1×, reserve slower)(folds into at-alloc-hot)Closest to jvm jp-string-concat-hot / cp-realloc-grow-one family
hot IO / print / loggp-fmt-hot fmt.*printf in loop (off)pp-print-hot print( (off)sp-print-hot print( (off)at-console-hot console.* (off)Env-bound; not escalated
delete / shapen/an/an/ano ArkTS delete rule (removed; Node proxy ~1×)Aligns with jsperf jp-delete-shape idea; ArkTS VM may differ
force-unwrap / unsafe optionaln/an/ano force-unwrap rule (removed; same time as if let)n/aSwift-specific
defer / chains / lookups in loopgp-defer-hot defer in loop (hint)n/ano map/joined rule (removed; about 1.2×)at-includes-hot includes lookup (hint); at-json-hot JSON.parse/stringify (hint)Kit-specific; no cross-kit mapping implied

cpp / csharp / php / ruby

SmellcppperfcsharpperfphpperfrubyperfNotes
buffer grow / capacityremoved (vector push without reserve — tombstone)cs-list-add-no-capacity List.Add without Capacity (off)ph-array-merge-rebind array_merge rebinding (error)rb-array-plus-rebind Array + / concat (off). Hash#merge was removed (literal build slower and more allocating on Ruby 4.0.8)Same idea as cp-realloc-grow-one / jvm jp-unpresized-collection; php is the only one with error (G4)
string builderno C++ string-append rule (removed; ~1.1×)cs-string-plus-hot string += (off)ph-string-dot-hot .= (off)rb-string-plus-hot string += (off)Closest to jvm jp-string-concat-hot / gp-string-plus-hot
hot IO / printremoved (iostream in loop — tombstone)cs-console-hot Console.Write* (off)ph-echo-hot echo/print (hint)rb-puts-hot puts/print (off)Env-bound; not escalated
regex in loop—cs-regex-hot regex in loop (hint)——Closest to jvm jp-regex-hot; weak wall, stays hint
bounds-checked indexremoved (.at() in loop — tombstone; reverse @-O2)———Prefer operator[] when index already validated; rule not shipped

cppperf vs cperf: cppperf is dedicated C++17; cperf remains C11 (+ light C++).

rust / kotlin / objc / dart / lua / beam

SmellrustperfkotlinperfobjcperfdartperfluaperfbeamperfNotes
buffer grow / capacityrs-alloc-hot alloc/clone (off); rs-collect-vec-hot collect-in-loop (hint); sized Vec::new plus push removed (~1× large fill)kt-list-add-hot list alloc (off)removed (NSMutableArray add — tombstone)dt-list-add-empty List.add (off); dt-growable-add-empty empty growable + add (hint)lu-table-insert-hot table.insert (off)be-list-concat-reduce list ++ (hint); be-map-merge-hot Map.merge (hint); be-enum-map-filter-chain map-then-filter (hint)Same idea as cp-realloc-grow-one
string builderrs-format-rebind-hot format! rebind (error, ~99×); amortized += removed (~1×)kt-string-plus-hot string += (off)oc-nsstring-append-hot stringByAppendingString (off)dt-string-plus-hot string + (off)lu-string-concat-hot .. (error)removed (<> append — tombstone; mixed/reverse)Closest to jvm jp-string-concat-hot; Rust splits amortized vs format!
hot IO / printrs-println-hot println! (hint)kt-println-hot println (off)oc-nslog-hot NSLog (off)dt-print-hot print (off)lu-print-hot print (hint)be-io-puts-hot IO.puts / io:format (off)Env-bound; not escalated

kotlinperf is Kotlin-first heuristics; measured JVM/JMH evidence stays in jvmperf (do not duplicate). objcperf ≠ swiftperf. beamperf covers .ex/.exs/.erl.

nim

SmellnimperfNotes
buffer grow / capacitynm-seq-add-hot seq .add in loop (off after G3); nm-seq-no-capacity empty seq + .add (hint)OK-idiom on Nim/Arraymancer/nimbus; opt-in. No JSONL (G4 blocked, no nim)
string builderremoved (&= / s = s & x — tombstone; join was slower)Closest to jvm jp-string-concat-hot / rb-string-plus-hot
hot IO / printnm-echo-hot echo in loop (off)Env-bound; not escalated

Empty / thin cells (honest)

GapWhy
cperf SoA lintGuide examples only. cp-wide-bool-flags is a hint for four full-width boolean flags and is not a SoA rule
jsperf print mapped but severity off (app)Measured, env-bound
jsperf buffer-grow dedicated ruleCovered loosely via jp-alloc-hot prealloc fixtures; jp-string-plus-in-loop was removed after the join bench missed ~2×
zperf sync/lockNo rule
Cross-kit holey outside jsPhysics differ → n/a by design
jvm jp-android-sparse-array … jp-ui-text-appendNeed ART/device
zperf zp-packable-bools / zp-usize-index-field / zp-optional-scalar-field densityPending density harness
jvm jp-sleep-polling sleep-pollingpending — design/situational, not micro
vt-unsafe-threadlocal-poolGuide-first; not in Rule.RULES
nimperf evidenceG4 blocked (no Nim toolchain on the measurement host); stubs only, no JSONL
arktsperf / objcperf evidenceMeasured on proxies (Node/V8, GNUstep), not the target runtime
swiftperf build on Linux CINeeds Swift toolchain; smoke skips when absent
csharpperf build without SDKNeeds dotnet; smoke skips when absent
arktsperf device benchesPrefer HarmonyOS device; Node proxy alone does not escalate
cppperf vs cperf overlapDeliberate split: C++ kit separate from C11 kit

Evidence pointers

KitPolicyCorrelate / batch (out of repo)
sharedrule-regression.mdthis catalog
zperfpackages/zperf/docs/evidence-policy.mdpackages/zperf/evidence/
cperfpackages/cperf/docs/evidence-policy.mdSTATIC-VS-O2 (not in repo)
jsperfpackages/jsperf/docs/evidence-policy.mdSTATIC-VS-V8 (not in repo)
jvmperfpackages/jvmperf/docs/evidence-policy.mdSTATIC-VS-HOTSPOT, EVIDENCE-BATCH (not in repo)

The correlate and batch notes named above are not part of this git repository — they are listed as plain names so the GitHub UI does not 404.


Hot compile / parse / query (2026-10-23)

These slugs are hint. Fixtures are paired. There is no JSONL, and they are not part of the 2026-10-22 corpus verdict.

KitSlugSmell
pyperfpp-json-hotjson.loads / dumps in loop
pyperfpp-deepcopy-hotdeepcopy in loop
jsperfjp-regex-ctor-hotnew RegExp (stable pattern; default scope all)
jsperfjp-json-hotJSON.parse / stringify (stable text; default scope all)
jsperfjp-dom-query-hotquerySelector / getElementById (stable selector; default scope all)

| jsperf | jp-query-all-star | querySelectorAll('*') full-tree walk (templates too) | | jsperf | jp-execjs-in-timer | built executeJavaScript/evaluate inside timers | | jsperf | jp-clone-per-event | structuredClone / JSON round-trip of event-like args | | goperf | gp-regexp-compile-hot | regexp.Compile in loop | | goperf | gp-json-marshal-hot | json.Marshal / Unmarshal in loop | | goperf | gp-reflect-hot | reflect.TypeOf / ValueOf in loop | | cppperf | cx-regex-ctor-hot | std::regex constructed in loop | | cppperf | cx-endl-hot | endl flush in loop | | csharpperf | cs-linq-materialize-hot | ToList / ToArray / ToDictionary in loop | | csharpperf | cs-format-hot | string.Format in loop | | rubyperf | rb-regexp-new-hot | Regexp.new in loop | | rubyperf | rb-json-hot | JSON.parse / generate in loop | | rustperf | rs-regex-new-hot | Regex::new in loop |