Skip to content

Conversation

@Phillip9587
Copy link
Member

This change refactors the JSON parser to eliminate the per-request if (strict) conditional check from the parsing hot path.

Instead of checking strict mode during parsing, we now create specialized parser functions at middleware initialization based on the strict option.

I'm aware that this introduces some code duplication between the strict and non-strict parser implementations, but I think that is a necessary trade-off.

Refactor JSON parser creation to return specialized parser functions
based on strict mode setting, removing the per-request `if (strict)`
conditional check in the parsing hot path.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the JSON parser logic by extracting the parse function into a separate helper function createJsonParser. The refactoring improves code organization by separating parser creation from middleware setup, and fixes an incorrect JSDoc return type annotation.

Key Changes

  • Extracted inline parse function into a new createJsonParser helper function that returns the appropriate parser based on the strict option
  • Corrected JSDoc return type annotation for firstchar function from {function} to {string | undefined}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Phillip9587
Copy link
Member Author

Ref: expressjs/perf-wg#77

A (Baseline):

npm run test:load -- --test=@expressjs/perf-load-body-parser --write

B (This branch):

npm run test:load -- --test=@expressjs/perf-load-body-parser --write --overrides='{"body-parser":"Phillip9587/body-parser#perf-json-strict-hot-path"}'

My local load test results:

A Results: ./perf/load/body-parser/results/result-1764606166571.json
====================================================================

┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬──────────┬─────────┐
│ Stat    │ 2.5%   │ 50%    │ 97.5%   │ 99%     │ Avg       │ Stdev    │ Max     │
├─────────┼────────┼────────┼─────────┼─────────┼───────────┼──────────┼─────────┤
│ Latency │ 678 ms │ 724 ms │ 1003 ms │ 1202 ms │ 753.07 ms │ 96.18 ms │ 1901 ms │
└─────────┴────────┴────────┴─────────┴─────────┴───────────┴──────────┴─────────┘
┌───────────┬────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────┐
│ Stat      │ 1%     │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev    │ Min    │
├───────────┼────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Req/Sec   │ 458    │ 10,335  │ 19,951  │ 21,423  │ 19,303.24 │ 3,059.32 │ 458    │
├───────────┼────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Bytes/Sec │ 322 kB │ 7.54 MB │ 14.5 MB │ 15.6 MB │ 14.1 MB   │ 2.23 MB  │ 321 kB │
└───────────┴────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────┘

Req/Bytes counts sampled once per second.
# of samples: 60

1173k requests in 60.03s, 844 MB read
83 errors (0 timeouts)

B Results: ./perf/load/body-parser/results/result-1764606062525.json
====================================================================

┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬─────────┐
│ Stat    │ 2.5%   │ 50%    │ 97.5%  │ 99%    │ Avg       │ Stdev    │ Max     │
├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼─────────┤
│ Latency │ 594 ms │ 609 ms │ 844 ms │ 970 ms │ 628.79 ms │ 66.41 ms │ 1548 ms │
└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴─────────┘
┌───────────┬────────┬─────────┬─────────┬─────────┬───────────┬──────────┬────────┐
│ Stat      │ 1%     │ 2.5%    │ 50%     │ 97.5%   │ Avg       │ Stdev    │ Min    │
├───────────┼────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Req/Sec   │ 409    │ 17,279  │ 21,295  │ 21,727  │ 20,632.29 │ 2,789.21 │ 409    │
├───────────┼────────┼─────────┼─────────┼─────────┼───────────┼──────────┼────────┤
│ Bytes/Sec │ 307 kB │ 12.6 MB │ 15.5 MB │ 15.8 MB │ 15 MB     │ 2.03 MB  │ 307 kB │
└───────────┴────────┴─────────┴─────────┴─────────┴───────────┴──────────┴────────┘

Req/Bytes counts sampled once per second.
# of samples: 60

1251k requests in 60.02s, 902 MB read
4 errors (0 timeouts)

Comparison: B Wins
==================
{
  diff: { rps: '-6.44%', throughput: '-6.44%', latency: '19.76%' },
  a: {
    avgRPS: 19303.24,
    avgThroughput: 14071464.54,
    avgLatency: 753.07,
    status: { '200': { count: 1158142 } }
  },
  b: {
    avgRPS: 20632.29,
    avgThroughput: 15040637.87,
    avgLatency: 628.79,
    status: { '200': { count: 1237872 } }
  }
}

cc: @wesleytodd

@wesleytodd
Copy link
Member

Ah awesome, yeah the perf tests are still a work in progress. So there is inconsistencies between runs and sometimes you may want to change the test requests or server implementation a bit to get real results. I just threw together some low effort requests with bodies in that PR but feel free to write ones that show the perf improvement even more than this did on your machine. And any feedback on running these is welcome!

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