Four things the export engine was doing wrong
Four faults, found by measuring rather than by looking. Three of them cost time and one of them handed people the wrong picture — which is the more expensive of the two.
A loop drew the same frame five times
Looping an export repeats the animation; it does not change it. Frame 360 of a six second piece played five times is frame 0 again, pixel for pixel. It was being drawn again anyway — an eighteen hundred frame export produced three hundred and sixty distinct images and paid for all eighteen hundred renders, then kept five copies of each on disk.
Only the unique frames are drawn now, and the repeats are hard links to them. The encoder and the archive writer still see a complete, correctly numbered sequence, so neither had to change.
Measured on the live renderer, the same project either way: one pass took 333 seconds for 360 frames. Five passes took under 298 seconds for 1,800. Five times the output in less time than a single pass had taken.
And checked rather than assumed — frames 100, 460, 820, 1180 and 1540 pulled out of the finished file hash identically. The repeat is the original, not a second drawing that happens to look the same.
Two renderers that disagreed about 3D
An export runs in one of two places: the cloud, which draws the same page the editor draws, or this tab, which redraws it with canvas primitives. The two are held to the same picture, and a sweep across the whole library found they agree to the pixel — except on the 3D shelf.
Forty of six hundred and fifty-two comparisons failed, all of them 3D. The reason is in the picture below: the model arrives unlit rather than absent. That is why the frame-size check written for an earlier 3D fault could not see it — an unlit object still varies in size from frame to frame, so every measurement said the export was fine.
A project holding a 3D layer now goes to the renderer the editor uses. The panel says so rather than quietly choosing. Everything else still exports in the tab, for nothing, faster than it plays.
The canvas path is worth fixing on its own. Until it is, nobody is handed the result.

A material that rebuilt the world to change colour
Animating a 3D material from gold to chrome arrived last week. What it also did was rebuild the model on every frame: the rebuild is keyed on the scene settings, gold and chrome differ in roughness, and so a six second journey rebuilt the geometry, the textures and the environment three hundred and sixty times over.
Measured at 4K: 267 milliseconds a frame without the travel, 779 with it. Three minutes added to a single export by a feature meant to cost nothing.
Metalness and roughness are one number each on a material that is already on the screen, exactly as the colour is. They are set in place now, and the same journey costs 263 milliseconds a frame — the price of the scene, and nothing for the material moving through it. Animating either of them directly was paying the same rebuild, and no longer does.
The renderer was starved, not slow
Frames are drawn in parallel tabs, and the number of them is sized from the processor count. Inside a container that count is the host's, not the container's: a service given two processors sees whatever the underlying machine has, and opens tabs for hardware nobody gave it. That was fixed first — the allowance is read from the container's own limit now.
Which then made the real question answerable. At four processors the renderer ran three lanes with 1.3 processors each and drew 2.49 frames a second. At eight it runs four lanes with two each and draws 4.42.
That is 1.78 times faster, and the interesting half is that only 1.33 of it comes from the extra lane. The rest is each lane finally having enough processor to work with — which is what starvation looks like when you measure it rather than guess at it. A 3D export at 720p went from about 200 seconds to a little over 100.
It costs 1.13 times as much per export: twice the rate per second, finished in a little over half the time.

And two smaller things
An export that failed used to hand over the browser automation library's own words — "page.waitForFunction: Timeout 30000ms exceeded" — on the screen of somebody paying for a product. There is nothing in that anybody outside this repository can act on. The machinery gets a sentence now, and the original goes to the log, where it is useful.
And a project holding a keyframe past its own end could not be saved at all, which meant it could not be exported either, since an export saves first. Those keys cannot play — the video stops before them — so they are cleared and the export goes on.