Skip to content

⚡️ Faster fc.record on generate#7046

Merged
dubzzz merged 2 commits into
mainfrom
claude/perf-record-mapper-direct-assign
Jun 4, 2026
Merged

⚡️ Faster fc.record on generate#7046
dubzzz merged 2 commits into
mainfrom
claude/perf-record-mapper-direct-assign

Conversation

@dubzzz

@dubzzz dubzzz commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Description

AI-agent disclosure: this PR was authored by an automated agent (Claude Code) and has not been line-by-line reviewed by a human before submission.

This speeds up the generate path of fc.record(...). Records are extremely common building blocks (directly, and inside letrec/memo/entityGraph/anything), so making their construction cheaper benefits a wide range of property-based tests. There is no observable behavior change: generated records have the exact same keys, values, property attributes (configurable/enumerable/writable) and prototype as before — this is a pure performance change with patch impact.

What changed

The mapper that turns generated values into the final record object (valuesAndSeparateKeysToObjectMapper) used Object.defineProperty for every present key:

safeObjectDefineProperty(obj, keys[idx], { value, configurable: true, enumerable: true, writable: true });

Object.defineProperty with all three flags set to true produces exactly the same own data property as a plain assignment obj[key] = value, but in V8 it is markedly slower and tends to push the freshly created object onto a slower path. The hot loop now assigns directly, which keeps the object on the fast path and removes a per-property builtin call.

The single exception is the string key "__proto__": a plain assignment would trigger the prototype setter on a regular object instead of defining an own property, so that one key keeps using defineProperty. Symbol keys and every other string key (including constructor, toString, …) are plain data properties on Object.prototype and are safely created by assignment, so they take the fast path.

Why this design

  • It is the minimal, surgical change: one mapper, one __proto__ guard, identical observable output.
  • The __proto__-only guard is provably sufficient — __proto__ is the only accessor on Object.prototype; every other "dangerous" key (constructor, toString, …) is a plain data property that assignment safely shadows. The existing dangerous-key unit tests cover exactly this and still pass.

Measurements

Isolated mapper microbenchmark (lower is better):

keys Object.defineProperty direct assignment
1 ~356 ns/op ~15 ns/op
3 ~1049 ns/op ~78 ns/op

End-to-end record(...).generate(...) improves by roughly 20–34% on multi-key records (the RNG dominates single-key records, where the mapper work is negligible). No regression observed on single-key records.

Tests

No new tests were required: the behavior is unchanged and the existing suites already pin it down. test/unit/arbitrary/_internals/mappers/ValuesAndSeparateKeysToObject.spec.ts (incl. the "__proto__"-as-key cases), record.spec.ts, and PartialRecordArbitraryBuilder.spec.ts all pass (47 tests), and tsc is clean. A changeset (patch) is included.

Checklist

Don't delete this checklist and make sure you do the following before opening the PR

  • I have a full understanding of every line in this PR — whether the code was hand-written, AI-generated, copied from external sources or produced by any other tool
  • I flagged the impact of my change (minor / patch / major) either by running pnpm run bump or by following the instructions from the changeset bot
  • I kept this PR focused on a single concern and did not bundle unrelated changes
  • I followed the gitmoji specification for the name of the PR, including the package scope (e.g. 🐛(vitest) Something...) when the change targets a package other than fast-check
  • I added relevant tests and they would have failed without my PR (when applicable)

Generated by Claude Code

Build generated records with direct property assignment instead of
Object.defineProperty in the values-to-object mapper.

Object.defineProperty with { configurable, enumerable, writable } all set
to true produces exactly the same own data property as a plain
assignment, but is significantly slower in V8 and pushes the freshly
created object onto a slower path. Switching to assignment keeps the
object on the fast path and avoids the per-property builtin call.

The only key that cannot use a plain assignment is the string
"__proto__" (it would trigger the prototype setter on a regular object
instead of defining an own property), so it keeps using defineProperty.
Symbol keys and every other string key are assigned directly.

Isolated mapper microbenchmark (objects/sec, higher is better):
  - 1 key:  ~356 ns/op -> ~15 ns/op
  - 3 keys: ~1049 ns/op -> ~78 ns/op
End-to-end record generate improves by ~20-34% on multi-key records.
@changeset-bot

changeset-bot Bot commented Jun 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 92d476f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
fast-check Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 4, 2026

Copy link
Copy Markdown
@fast-check/ava

npm i https://pkg.pr.new/@fast-check/ava@7046

fast-check

npm i https://pkg.pr.new/fast-check@7046

@fast-check/jest

npm i https://pkg.pr.new/@fast-check/jest@7046

@fast-check/packaged

npm i https://pkg.pr.new/@fast-check/packaged@7046

@fast-check/poisoning

npm i https://pkg.pr.new/@fast-check/poisoning@7046

@fast-check/vitest

npm i https://pkg.pr.new/@fast-check/vitest@7046

@fast-check/worker

npm i https://pkg.pr.new/@fast-check/worker@7046

commit: 92d476f

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

⏱️ Benchmark Results

Click to expand

 RUN  v4.1.5 /home/runner/work/fast-check/fast-check


 ✓  fast-check  test/bench/arbitraries.bench.ts > generate 45969ms
     name                                                         hz     min      max    mean     p75     p99    p995     p999      rme  samples
   · boolean()                                          6,928,249.36  0.0001   0.7288  0.0001  0.0001  0.0008  0.0013   0.0018   ±0.38%  3464125  [0.99x] ⇓
     boolean()                                          6,963,491.89  0.0001   0.2650  0.0001  0.0001  0.0002  0.0002   0.0005   ±0.44%  3481746  (baseline)
   · integer()                                          7,287,697.56  0.0001   0.0436  0.0001  0.0002  0.0003  0.0003   0.0006   ±0.08%  3643849  [1.03x] ⇑
     integer()                                          7,050,448.72  0.0001   0.2343  0.0001  0.0002  0.0002  0.0002   0.0003   ±0.15%  3525225  (baseline)
   · maxSafeInteger()                                   5,786,378.63  0.0001   0.2877  0.0002  0.0002  0.0003  0.0004   0.0006   ±0.22%  2893190  [1.10x] ⇑
     maxSafeInteger()                                   5,256,234.59  0.0001   0.3917  0.0002  0.0002  0.0003  0.0003   0.0005   ±0.32%  2628118  (baseline)
   · bigInt()                                             933,285.12  0.0007   0.4162  0.0011  0.0012  0.0017  0.0019   0.0082   ±0.33%   466643  [0.78x] ⇓
     bigInt()                                           1,195,214.61  0.0006   0.4843  0.0008  0.0012  0.0013  0.0017   0.0036   ±0.41%   597608  (baseline)
   · float()                                            4,562,797.58  0.0002   0.4921  0.0002  0.0002  0.0004  0.0005   0.0009   ±0.29%  2281400  [1.03x] ⇑
     float()                                            4,428,697.56  0.0002   0.5763  0.0002  0.0002  0.0003  0.0004   0.0008   ±0.42%  2214349  (baseline)
   · double()                                           1,604,384.15  0.0004   3.6999  0.0006  0.0007  0.0012  0.0013   0.0026   ±4.23%   803688  [0.95x] ⇓
     double()                                           1,688,516.48  0.0004   0.3113  0.0006  0.0007  0.0009  0.0013   0.0020   ±0.33%   844259  (baseline)
   · date()                                             2,821,984.31  0.0003   3.0136  0.0004  0.0003  0.0007  0.0008   0.0022   ±2.65%  1410993  [0.93x] ⇓
     date()                                             3,033,095.36  0.0003   0.5185  0.0003  0.0003  0.0004  0.0005   0.0012   ±0.35%  1516548  (baseline)
   · string()                                             804,187.19  0.0003   8.0202  0.0012  0.0013  0.0034  0.0045   0.0139   ±7.49%   403365  [0.80x] ⇓
     string()                                           1,010,870.48  0.0002   0.5716  0.0010  0.0012  0.0022  0.0032   0.0115   ±0.52%   505436  (baseline)
   · string({ maxLength: 100, size: 'max' })              174,397.82  0.0003   3.6383  0.0057  0.0077  0.0147  0.0161   0.0228   ±4.54%    87200  [0.96x] ⇓
     string({ maxLength: 100, size: 'max' })              180,894.62  0.0003   0.4880  0.0055  0.0080  0.0159  0.0170   0.0241   ±0.68%    90448  (baseline)
   · string({ unit: 'grapheme' })                         546,333.84  0.0003   9.0421  0.0018  0.0020  0.0049  0.0062   0.0106   ±9.93%   273167  [0.82x] ⇓
     string({ unit: 'grapheme' })                         662,643.83  0.0002   0.5275  0.0015  0.0020  0.0036  0.0042   0.0129   ±0.51%   331322  (baseline)
   · base64String()                                       825,145.69  0.0003   7.0057  0.0012  0.0013  0.0034  0.0048   0.0097   ±7.05%   412574  [0.86x] ⇓
     base64String()                                       962,395.44  0.0003   0.6004  0.0010  0.0013  0.0023  0.0033   0.0116   ±0.64%   481198  (baseline)
   · array(integer())                                   1,335,526.69  0.0002   8.0252  0.0007  0.0007  0.0018  0.0021   0.0056   ±7.57%   667764  [0.79x] ⇓
     array(integer())                                   1,689,707.68  0.0002   0.5697  0.0006  0.0006  0.0015  0.0022   0.0042   ±0.53%   844855  (baseline)
   · array(integer(), { maxLength: 100, size: 'max' })    311,222.43  0.0002   3.6314  0.0032  0.0040  0.0090  0.0096   0.0154   ±5.68%   155613  [0.91x] ⇓
     array(integer(), { maxLength: 100, size: 'max' })    341,174.36  0.0002   0.4942  0.0029  0.0039  0.0101  0.0107   0.0181   ±0.67%   170588  (baseline)
   · uniqueArray(integer())                             1,012,152.70  0.0002   8.3314  0.0010  0.0011  0.0025  0.0031   0.0084   ±6.60%   506077  [0.87x] ⇓
     uniqueArray(integer())                             1,169,428.18  0.0002  24.8560  0.0009  0.0010  0.0021  0.0027   0.0056   ±9.76%   584715  (baseline)
   · set(integer())                                       861,055.96  0.0003   4.3572  0.0012  0.0013  0.0024  0.0026   0.0088   ±5.73%   430528  [0.85x] ⇓
     set(integer())                                     1,014,488.52  0.0003   0.4405  0.0010  0.0012  0.0023  0.0024   0.0109   ±0.42%   507245  (baseline)
   · tuple(integer())                                   2,438,403.76  0.0002   7.3162  0.0004  0.0003  0.0009  0.0010   0.0028  ±10.82%  1219202  [0.65x] ⇓
     tuple(integer())                                   3,726,739.42  0.0002   0.4005  0.0003  0.0003  0.0004  0.0005   0.0012   ±0.51%  1863370  (baseline)
   · record({ a: integer() })                             971,379.54  0.0004  12.1174  0.0010  0.0008  0.0020  0.0029   0.0084  ±14.11%   485690  [0.76x] ⇓
     record({ a: integer() })                           1,270,135.71  0.0006   0.4298  0.0008  0.0008  0.0012  0.0015   0.0024   ±0.45%   635069  (baseline)
   · dictionary(string(), integer())                       82,237.97  0.0005   7.4895  0.0122  0.0152  0.0269  0.0305   0.0438   ±9.46%    41120  [0.74x] ⇓
     dictionary(string(), integer())                      110,583.32  0.0005   3.9706  0.0090  0.0130  0.0223  0.0247   0.0328   ±2.38%    55292  (baseline)
   · map(string(), integer())                             126,366.16  0.0003  12.4193  0.0079  0.0096  0.0240  0.0307   0.0408  ±10.38%    63184  [0.73x] ⇓
     map(string(), integer())                             172,187.59  0.0003   0.4200  0.0058  0.0087  0.0148  0.0172   0.0245   ±0.65%    86095  (baseline)
   · constantFrom(...)                                  8,662,453.45  0.0001   0.6055  0.0001  0.0001  0.0003  0.0003   0.0005   ±0.33%  4331228  [1.16x] ⇑
     constantFrom(...)                                  7,460,684.81  0.0001   0.2687  0.0001  0.0001  0.0002  0.0002   0.0003   ±0.15%  3730343  (baseline)
   · oneof(integer(), integer())                        4,089,248.26  0.0002   0.0410  0.0002  0.0003  0.0004  0.0005   0.0008   ±0.09%  2044625  [1.12x] ⇑
     oneof(integer(), integer())                        3,644,326.84  0.0002   0.0455  0.0003  0.0003  0.0004  0.0004   0.0009   ±0.12%  1822164  (baseline)
   · oneof({ weight, arbitrary }, ...)                  4,161,997.86  0.0002   0.0411  0.0002  0.0003  0.0004  0.0005   0.0008   ±0.09%  2080999  [1.14x] ⇑
     oneof({ weight, arbitrary }, ...)                  3,663,028.90  0.0002   0.6333  0.0003  0.0003  0.0004  0.0004   0.0010   ±0.27%  1831515  (baseline)
   · option(integer())                                  3,994,174.63  0.0002   0.8737  0.0003  0.0003  0.0005  0.0006   0.0009   ±0.48%  1997088  [1.09x] ⇑
     option(integer())                                  3,660,727.58  0.0002   0.5137  0.0003  0.0003  0.0004  0.0004   0.0011   ±0.34%  1830364  (baseline)
   · subarray([1, 2, 3, 4, 5])                          1,680,177.84  0.0002   0.4588  0.0006  0.0008  0.0013  0.0014   0.0020   ±0.44%   840089  [0.85x] ⇓
     subarray([1, 2, 3, 4, 5])                          1,973,071.18  0.0002   0.5455  0.0005  0.0007  0.0010  0.0012   0.0018   ±0.44%   986536  (baseline)
   · letrec(tree)                                         506,123.79  0.0002   9.9990  0.0020  0.0022  0.0069  0.0087   0.0141  ±13.30%   253062  [0.94x] ⇓
     letrec(tree)                                         535,587.89  0.0002   0.4938  0.0019  0.0027  0.0081  0.0093   0.0158   ±0.64%   267794  (baseline)
   · memo(tree)                                           513,522.67  0.0002   9.9350  0.0019  0.0024  0.0068  0.0094   0.0136  ±13.20%   259868  [0.96x] ⇓
     memo(tree)                                           535,038.72  0.0002   0.5078  0.0019  0.0033  0.0068  0.0072   0.0154   ±0.64%   267520  (baseline)
   · anything()                                            62,973.00  0.0003   9.7844  0.0159  0.0227  0.0705  0.0816   0.1079  ±10.86%    31487  [0.77x] ⇓
     anything()                                            81,857.92  0.0003   2.2059  0.0122  0.0203  0.0634  0.0723   0.0989   ±2.22%    40929  (baseline)
   · json()                                                57,255.54  0.0003  10.6226  0.0175  0.0245  0.0770  0.0894   0.1161  ±11.24%    28794  [0.76x] ⇓
     json()                                                75,468.84  0.0004   5.0457  0.0133  0.0219  0.0664  0.0768   0.1033   ±2.80%    37736  (baseline)
   · entityGraph(node -> node)                              6,208.89  0.0091  30.0221  0.1611  0.1253  0.3700  0.4734  12.3106  ±19.34%     3118  [0.87x] ⇓
     entityGraph(node -> node)                              7,117.97  0.0088  10.8812  0.1405  0.1303  0.2399  0.2901  10.4778  ±13.38%     3560  (baseline)
   · emailAddress()                                        66,054.08  0.0027   9.0619  0.0151  0.0149  0.0385  0.0454   0.0727  ±11.62%    33116  [0.77x] ⇓
     emailAddress()                                        85,925.43  0.0024  25.9800  0.0116  0.0138  0.0309  0.0395   0.0524  ±10.23%    42963  (baseline)
   · webUrl()                                             119,379.89  0.0023   9.3865  0.0084  0.0074  0.0137  0.0164   0.0253  ±13.31%    59691  [0.70x] ⇓
     webUrl()                                             169,725.08  0.0023   0.4372  0.0059  0.0069  0.0109  0.0164   0.0234   ±0.56%    84863  (baseline)
   · ipV4()                                             1,122,284.02  0.0006   6.6015  0.0009  0.0009  0.0014  0.0017   0.0069   ±6.60%   561143  [0.88x] ⇓
     ipV4()                                             1,278,224.34  0.0006   0.4997  0.0008  0.0008  0.0011  0.0014   0.0026   ±0.44%   639113  (baseline)
   · ipV6()                                               169,963.55  0.0005   9.3761  0.0059  0.0053  0.0141  0.0159   0.0264  ±11.81%    85289  [0.70x] ⇓
     ipV6()                                               241,773.95  0.0005   0.4466  0.0041  0.0049  0.0072  0.0098   0.0183   ±0.55%   120888  (baseline)
   · uuid()                                               515,996.74  0.0010   9.1989  0.0019  0.0017  0.0035  0.0042   0.0100   ±9.71%   257999  [0.73x] ⇓
     uuid()                                               710,355.59  0.0010   0.4813  0.0014  0.0014  0.0021  0.0024   0.0122   ±0.46%   355178  (baseline)
   · stringMatching(/^[a-zA-Z0-9]+$/)                     404,959.16  0.0007   8.7491  0.0025  0.0024  0.0037  0.0044   0.0114  ±11.79%   204764  [0.81x] ⇓
     stringMatching(/^[a-zA-Z0-9]+$/)                     501,869.47  0.0007   0.5063  0.0020  0.0025  0.0038  0.0041   0.0137   ±0.56%   250935  (baseline)
   · mixedCase(string())                                  464,569.83  0.0005   6.7484  0.0022  0.0025  0.0054  0.0065   0.0117   ±4.85%   232286  [0.87x] ⇓
     mixedCase(string())                                  533,454.54  0.0005   0.5324  0.0019  0.0023  0.0036  0.0040   0.0134   ±0.54%   266728  (baseline)
   · integer().map(.)                                   5,360,804.43  0.0001   0.3762  0.0002  0.0002  0.0003  0.0004   0.0007   ±0.36%  2680403  [1.05x] ⇑
     integer().map(.)                                   5,113,599.52  0.0001   0.3314  0.0002  0.0002  0.0003  0.0003   0.0007   ±0.50%  2556800  (baseline)
   · integer().chain(.)                                 2,990,851.60  0.0002   0.5413  0.0003  0.0004  0.0006  0.0008   0.0011   ±0.67%  1495426  [1.01x] ⇑
     integer().chain(.)                                 2,949,951.16  0.0002   0.5194  0.0003  0.0004  0.0005  0.0006   0.0013   ±0.56%  1474976  (baseline)
   · integer().filter(.)                                3,454,227.54  0.0001   0.3747  0.0003  0.0003  0.0008  0.0009   0.0012   ±0.30%  1727114  [1.13x] ⇑
     integer().filter(.)                                3,050,499.03  0.0001   0.2944  0.0003  0.0004  0.0010  0.0011   0.0015   ±0.21%  1525250  (baseline)

 ✓  fast-check  test/bench/runners.bench.ts > runner 2426ms
     name                                                     hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · assert on sync predicate not returning anything   13,138.33  0.0715  0.3741  0.0761  0.0753  0.1580  0.1700  0.2019  ±0.41%     6570  [1.05x] ⇑
     assert on sync predicate not returning anything   12,476.54  0.0772  0.3477  0.0802  0.0782  0.1549  0.1651  0.2072  ±0.36%     6239  (baseline)
   · assert on sync predicate returning true           13,123.23  0.0711  0.3013  0.0762  0.0744  0.1602  0.1719  0.2268  ±0.48%     6562  [1.06x] ⇑
     assert on sync predicate returning true           12,407.32  0.0770  0.3582  0.0806  0.0781  0.1560  0.1748  0.2348  ±0.44%     6204  (baseline)
   · assert on async predicate not returning anything   7,898.44  0.1101  0.3328  0.1266  0.1272  0.2091  0.2149  0.2333  ±0.42%     3950  [0.94x] ⇓
     assert on async predicate not returning anything   8,378.16  0.1115  8.0690  0.1194  0.1141  0.1944  0.1995  0.2355  ±3.14%     4190  (baseline)
   · check on sync predicate not returning anything    13,144.92  0.0711  0.3405  0.0761  0.0751  0.1617  0.1745  0.2189  ±0.45%     6573  [1.05x] ⇑
     check on sync predicate not returning anything    12,493.71  0.0771  0.3577  0.0800  0.0781  0.1553  0.1654  0.2445  ±0.38%     6247  (baseline)

 BENCH  Summary

   fast-check  constantFrom(...) - test/bench/arbitraries.bench.ts > generate
    1.19x faster than integer()
    1.25x faster than boolean()
    1.50x faster than maxSafeInteger()
    1.62x faster than integer().map(.)
    1.90x faster than float()
    2.08x faster than oneof({ weight, arbitrary }, ...)
    2.12x faster than oneof(integer(), integer())
    2.17x faster than option(integer())
    2.51x faster than integer().filter(.)
    2.90x faster than integer().chain(.)
    3.07x faster than date()
    3.55x faster than tuple(integer())
    5.16x faster than subarray([1, 2, 3, 4, 5])
    5.40x faster than double()
    6.49x faster than array(integer())
    7.72x faster than ipV4()
    8.56x faster than uniqueArray(integer())
    8.92x faster than record({ a: integer() })
    9.28x faster than bigInt()
    10.06x faster than set(integer())
    10.50x faster than base64String()
    10.77x faster than string()
    15.86x faster than string({ unit: 'grapheme' })
    16.79x faster than uuid()
    16.87x faster than memo(tree)
    17.12x faster than letrec(tree)
    18.65x faster than mixedCase(string())
    21.39x faster than stringMatching(/^[a-zA-Z0-9]+$/)
    27.83x faster than array(integer(), { maxLength: 100, size: 'max' })
    49.67x faster than string({ maxLength: 100, size: 'max' })
    50.97x faster than ipV6()
    68.55x faster than map(string(), integer())
    72.56x faster than webUrl()
    105.33x faster than dictionary(string(), integer())
    131.14x faster than emailAddress()
    137.56x faster than anything()
    151.29x faster than json()
    1395.17x faster than entityGraph(node -> node)

   fast-check  check on sync predicate not returning anything - test/bench/runners.bench.ts > runner
    1.00x faster than assert on sync predicate not returning anything
    1.00x faster than assert on sync predicate returning true
    1.66x faster than assert on async predicate not returning anything

Benchmark report written to /home/runner/work/fast-check/fast-check/benchmark.json

Commit: 92d476f (merge: ad83fd9)

@dubzzz
dubzzz enabled auto-merge (squash) June 4, 2026 21:48
@dubzzz
dubzzz merged commit 4c64e0e into main Jun 4, 2026
52 checks passed
@dubzzz
dubzzz deleted the claude/perf-record-mapper-direct-assign branch June 4, 2026 21:54
@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.87%. Comparing base (dc9f32a) to head (92d476f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7046   +/-   ##
=======================================
  Coverage   94.86%   94.87%           
=======================================
  Files         212      212           
  Lines        5884     5889    +5     
  Branches     1543     1546    +3     
=======================================
+ Hits         5582     5587    +5     
  Misses        294      294           
  Partials        8        8           
Flag Coverage Δ
tests 94.87% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants