perf(output): write rendered pages with parallel async I/O#3105
perf(output): write rendered pages with parallel async I/O#3105StoneCypher wants to merge 1 commit into
Conversation
| @@ -260,6 +260,8 @@ export class Renderer extends AbstractComponent<Application, RendererEvents> { | |||
|
|
|||
| renderStartTime = -1; | |||
|
|
|||
There was a problem hiding this comment.
This unnecessarily complicates things, instead of adding an extra field to the class it would be better to have renderDocument return the content to be written, then render is the only method that even has to consider this.
There was a problem hiding this comment.
that would lose the asynchronous parallelism that creates the speed win here
There was a problem hiding this comment.
Returning the string to be written, or the promise returned by writeFile, collecting that in an array in render, and waiting for it there would lose parallelism? I don't think so...
There was a problem hiding this comment.
the last time i turned down someone's pr, i still thanked them for the effort, and for caring enough to put time into my work
i think maybe you don't recognize that the way you've treated someone for trying to help is unkind
your tool really has been a big help over the years. i had really hoped that you would let me keep it a viable tool for me.
my github actions got locked this month, and when i looked, it was four things: mac builds on PRs being charged something like 30x the price of linux minutes, a mutation library called stryker, running the full build chain on every platform, and typedoc.
initially typedoc was about 12% of my build time. i invested a bunch of effort cutting mac out of everything but the merge to main, i cut the full build down to a single runner and the rest down to just the compile and testing, and i gave stryker some valium.
at that point, typedoc on repaired repos became 85% of my build minutes. with my current pace of development, that still means that i'm going to get locked out of github actions halfway through each month.
i'm going to use my fork of typedoc for a while, on the hope that sooner or later you're going to say something to yourself like "hey, that guy's helped out before, he was just trying to help this time, and when i run the benchmark against a larger doc set, it really does speed typedoc up a lot."
but it seems like you've positioned this conversation in a way that you derive being the wise one in the room from looking at a pr and exasperatedly pointing out what you believe are small defects, and saying "that means the entire thing is trash," without considering what the benchmark says.
i guess all the hand-written PRs are flawless. perhaps the benchmark lied to me convincingly, like claude said.
my fork is fast enough to give me some space to look for alternatives. i don't want to; my internal uses of your doc generator have a very heavily customized theme, and i would hate to have to remake it somewhere else.
unfortunately, typedoc is performance problematic enough that it's currently costing me eighty dollars a month in extra github actions minutes, and it's getting worse as i automate. this isn't a spend i'm able to keep up with.
the patches were enough, but it seems you aren't interested.
maybe you'll change your mind, and say "actually, thanks for the effort." not even about the code, just about being friendly to someone who was trying to pitch in. but i don't expect it.
good luck moving forwards.
What
The renderer's per-page write step in
Renderer.renderDocumentwas usingwriteFileSync(sync I/O) called once per page, for 271 sequential writes on a typedoc-on-typedoc run. Sibling output plugins (IconsPlugin,JavascriptIndexPlugin,HierarchyPlugin,SitemapPlugin,NavigationPlugin) already use asyncwriteFile+Promise.all. This PR brings the per-page write to the same pattern.Why
CPU profile attributes 427 ms inclusive in
writeFileSynccalled fromrenderDocument(out of ~1565 ms total per-page inclusive). On any SSD, 271 small writes (~5 KB each) pipeline well in parallel — much of that 427 ms can be absorbed by the OS / disk queue while the JavaScript main thread does nothing useful.How
Same architectural pattern as the async-git PR (#3104): keep the sync event handler synchronous, queue the work, drain in parallel from the outer async wrapper.
Renderergains a privatependingPageWrites: Array<{ filename, contents }>field.renderDocumentno longer callswriteFileSync— it pushes `{ filename, contents }` onto the queue.Renderer.renderdrains the queue withPromise.allSettledafter the page-render loop and before the existingpostRenderAsyncJobswork. `Promise.allSettled` (notPromise.all) preserves the prior "log and continue" behavior — one failed write does not abort the others.fs.writeFileSynccalls elsewhere in the file (for.nojekylland `CNAME`) are unaffected; those use a different import (import * as fs from \"fs\").Perf impact
Estimated 250-400 ms median improvement on the typedoc-on-typedoc workload. Final measurement landing here when CI reproduces it on the reference machine.
Test plan
Rollback
Revert this PR. The previous sync write path is fully self-contained — no shared API changes.
Stack context
Part of the typedoc speedup stack tracked by #3103. This PR is in Group B — independent of the async-git stack (Group A, #3104). It can land in any order relative to the other Group B PRs.
Blocks #3103