| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Every promise-based fs operation eagerly allocated two AliasedBuffers (a stats array and a statfs array) at request creation, although only stat-family resolutions ever read the first and only statfs() reads the second. Each allocation is an ArrayBuffer, a TypedArray and a strong v8::Global. The callback path has no equivalent cost since it resolves through a shared global array. Construct the arrays lazily in ResolveStat()/ResolveStatFs() instead. Once created the lifetime is unchanged, so deferred continuations still read from request-owned memory. Improves fs/promises throughput under concurrency: writeFile +53%, stat +26%, readFile +22% at 64 in-flight operations on tmpfs, with callback paths unchanged. Signed-off-by: Sam Attard <sattard@anthropic.com>
| if (stats_field_array_.has_value()) { | ||
| tracker->TrackField("stats_field_array", stats_field_array_.value()); | ||
| } | ||
| if (statfs_field_array_.has_value()) { |
There was a problem hiding this comment.
Not a blocker for this PR, but we should support std::optional in MemoryTracker instead of manually handling this case (e.g. add an overload like
Line 216 in d097de8
Sorry, something went wrong.
Sorry, something went wrong.
Sorry, something went wrong.
The stats field array of an FSReqPromise is now allocated lazily when the request resolves with stats, so a request that is still pending does not retain it and the edge cannot appear in a heap snapshot. Signed-off-by: Sam Attard <sattard@anthropic.com>
Sorry, something went wrong.
Every promise-based fs operation eagerly allocated two AliasedBuffers (a stats array and a statfs array) at request creation, although only stat-family resolutions ever read the first and only statfs() reads the second. Each allocation is an ArrayBuffer, a TypedArray and a strong v8::Global. The callback path has no equivalent cost since it resolves through a shared global array. Construct the arrays lazily in ResolveStat()/ResolveStatFs() instead. Once created the lifetime is unchanged, so deferred continuations still read from request-owned memory. Improves fs/promises throughput under concurrency: writeFile +53%, stat +26%, readFile +22% at 64 in-flight operations on tmpfs, with callback paths unchanged. Signed-off-by: Sam Attard <sattard@anthropic.com> PR-URL: #63886 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Every promise-based fs operation eagerly allocated two AliasedBuffers (a stats array and a statfs array) at request creation, although only stat-family resolutions ever read the first and only statfs() reads the second. Each allocation is an ArrayBuffer, a TypedArray and a strong v8::Global. The callback path has no equivalent cost since it resolves through a shared global array. Construct the arrays lazily in ResolveStat()/ResolveStatFs() instead. Once created the lifetime is unchanged, so deferred continuations still read from request-owned memory. Improves fs/promises throughput under concurrency: writeFile +53%, stat +26%, readFile +22% at 64 in-flight operations on tmpfs, with callback paths unchanged. Signed-off-by: Sam Attard <sattard@anthropic.com> PR-URL: #63886 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
| Back | FazBrowse Home | New Git URL |
Description of Change
Every promise-based fs operation allocates two AliasedBuffers at request construction — an 18-field stats array and an 8-field statfs array — although only stat-family resolutions ever read the first and only statfs() reads the second. Each one costs an ArrayBuffer, a TypedArray and a strong v8::Global, so a plain fsp.writeFile() pays for two backing stores it can never use. The callback path has no equivalent cost: FSReqCallback resolves stats through a shared per-process array.
Under concurrency this allocation churn is the dominant overhead of the promises path — profiling 64 in-flight 1KB reads shows ~21% of CPU ticks in the malloc family for promises vs ~5% for callbacks issuing identical syscalls.
This PR constructs the arrays lazily in ResolveStat()/ResolveStatFs():
benchmark/compare.js shows significant improvements on the concurrent configurations — writefile-promises +12.6…20.7%, readfile-promises +19.1…21.5% (unencoded), bench-stat-promise +2.2…3.5% — with sequential large-payload configs neutral (those are threadpool-latency-bound, as expected).
Benchmark resultsbenchmark/compare.js, 10 samples per configuration, Welch t-test (*** p<0.001, ** p<0.01, * p<0.05):
Additional ad-hoc microbenchmarks (tmpfs, 64 ops in flight): fsp.writeFile 1KB +53%, fsp.stat +26%, fsp.readFile 1KB +22% — with all callback variants unchanged (±3%), confirming attribution since callbacks never construct FSReqPromise. Scripts: https://gist.github.com/MarshallOfSound/7d22707e3e550a782e96f9da0081e9f1
Checklist