It is very difficult to parallelise game engines. It is not at all a simple matter of "divide the work over N cores", and this is not limited to Javascript either - it is similarly difficult to parallelise native engines for the same reason: game engine logic is highly sequential. Take the event sheet, which is required to process in top-to-bottom order for predictability when defining your game's logic (so you know what happens in what order). Any events referring to objects or variables which were used in any way in prior events simply must be run sequentially (i.e. after the previous events have finished running) in order to work correctly. Therefore, that work cannot be split off on to another core, or if it was, other cores would have to wait for the work to be done before continuing, which is no faster (and probably actually slower) than just running on one core.
Further parallelism comes with a synchronisation overhead. Every time work is sent off to another core, there is a performance overhead of sending the work to another core, waiting for the core to context switch to the thread, probable cache misses while it "warms up" to the new work, and then the same context switch and sending overhead to send the work back. As a result it's actually slower to send work to another core if it's a small amount of work - the overhead of arranging the off-core work will eclipse any benefit. For example if you have 100 instances running a "Set X to 0" action (which is very quick), trying to split that work over 4 cores running 25 instances each is likely far slower than just running it on the same thread. So not only is it difficult to parallelise the whole event sheet, it's difficult to parallelise individial events as well. For other engines, replace "events" with "logic", and it's similarly challenging for them to get useful performance gains on multi-core systems.
That's not to say there isn't a lot of parallelism going on - here's a list of things which modern browser engines run in parallel:
- audio processing
- network requests
- image/video decoding
- input (e.g. mouse/keyboard/gamepad input)
- draw calls (e.g. Chrome bundles up all WebGL calls and runs them on a separate thread)
- compositing (browser-level rendering of elements)
- the GPU itself is a large parallel processor running in parallel to the CPU
- pathfinding is CPU-intensive enough to run on a web worker on another core and benefit performance (this is actually a very nice feature since intense pathfinding does not impact the game framerate)
Browser developers are well aware of the need to split as much work as possible over different cores to achieve maximum performance, so work is continuing to add more parallel features. We're watching this carefully and will add support where practical.