Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Well-optimized JavaScript can get to within about 1.5x the performance of C++ - something we have experience with having developed a full game engine in JavaScript [1]. Why is the TypeScript team moving to an entirely different technology instead of working on optimizing the existing TS/JS codebase?

[1] https://www.construct.net/en



Well-optimized JavaScript can, if you jump through hoops like avoiding object creation and storing your data in `Uint8Array`s. But idiomatic, maintainable JS simply can't (except in microbenchmarks where allocations and memory layout aren't yet concerns).

In a game engine, you probably aren't recreating every game object from frame to frame. But in a compiler, you're creating new objects for every file you parse. That's a huge amount of work for the GC.


I'd say that our JS game engine codebase is generally idiomatic, maintainable JS. We don't really do anything too esoteric to get maximum performance - modern JS engines are phenomenal at optimizing idiomatic code. The best JS performance advice is to basically treat it like a statically typed language (no dynamically-shaped objects etc) - and TS takes care of that for you. I suppose a compiler is a very different use case and may do things like lean on the GC more, but modern JS GCs are also amazing.

Basically I'd be interested to know what the bottlenecks in tsc are, whether there's much low-hanging fruit, and if not why not.


Note that games are based on main loops + events, for which JITs are optimized, while compilers are typically single run-to-completion, for which JITs aren't.

So this might be a very different performance profile.

*edit* I had initially written "single-pass", but in the context of a compiler, that's ambiguous.


In other words you write asm.js, which is a textual form of WebAssembly that is also valid Javascript, and if your browser has an asm.js JIT compiler - which it doesn't because it was replaced by WebAssembly.


Our best estimate for how much faster the Go code is (in this situation) than the equivalent TS is ~3.5x

In a situation like a game engine I think 1.5x is reasonable, but TS has a huge amount of polymorphic data reading that defeats a lot of the optimizations in JS engines that get you to monomorphic property access speeds. If JS engines were better at monomorphizing access to common subtypes across different map shapes maybe it'd be closer, but no engine has implemented that or seems to have much appetite for doing so.


I used to work on compilers & JITs, and 100% this — polymorphic calls is the killer of JIT performance, which is why something native is preferable to something that JIT compiles.

Also for command-line tools, the JIT warmup time can be pretty significant, adding a lot to overall command-to-result latency (and in some cases even wiping out the JIT performance entirely!)


> If JS engines were better at monomorphizing access to common subtypes across different map shapes maybe it'd be closer, but no engine has implemented that or seems to have much appetite for doing so.

I really wish JS VMs would invest in this. The DOM is full of large inheritance hierarchies, with lots of subtypes, so a lot of DOM code is megamorphic. You can do tricks like tearing off methods from Element to use as functions, instead of virtual methods as usual, but that quite a pain.


"Well optimized Javascript", and more generally, "well-optimized code for a JIT/optimizer for language X", is a subset of language X, is an undefined subset of language X, is a moving subset of language X that is moving in ways unrelated to your project, is actually multiple such subsets at a minimum one per JIT and arguably one per version of JIT compilers, and is generally a subset of language X that is extremely complicated (e.g., you can lose optimization if your arrays grow in certain ways, or you can non-locally deoptimize vast swathes of your code because one function call in one location happened to do one thing the JIT can't handle and it had to despecialize everything touching it as a result) such that trying to keep a lot of developers in sync with the requirements on a large project is essentially infeasible.

None of these things say "this is a good way to build a large compiler suite that we're building for performance".


Please note that compilers and game engines have extremely different needs and performance characteristics—and also that statements like "about 1.5x the performance of C++" are virtually meaningless out-of-context. I feel we've long passed this type of performance discussion by and could do with more nuanced and specific discussions.


Who wants to spend all their time hand-tuning JS/TS when you can write the same code in Go, spend no time at all optimizing it, and get 10x better results?


Why is the TypeScript team moving to an entirely different technology

A few things mentioned in an interview:

Cannot build native binaries from TypeScript

Cannot as easily take advantage of concurrency in TypeScript

Writing fast TypeScript requires you to write things in a way that isn't 'normal' idiomatic TypeScript. Easier to onboard new people onto a more idiomatic codebase.


The message I hear is: don't use JS, don't use async. Music to my ears.


All Go is async though.


What kind of C++ and what kind of JS?

- C++ with thousands of tiny objects and virtual function calls? - JavaScript where data is stored in large Int32Array and does operations on it like a VM?

If you know anything about how JavaScript works, you know there is a lot of costly and challenging resource management.


While Go can be considered entirely different technology, I'd argue that Go is easy enough to understand for the vast majority of software developers that it's not too difficult to learn.

(disclaimer: I am a biased Go fan)


It had been very explicitly designed with this goal. The idea was to make a simpler Java which is as easy as possible to deploy and as fast as possible to commute and by these measures is a resounding success.


Does "well-optimized JavaScript" mean "you can't use Objects"?

In JavaScript, you can't even put 8M keys in a Hashmap; inserts take > 1 second per element:

https://issues.chromium.org/issues/42202799


Sometimes, the time required to optimize is greater than the time required to rewrite.


Well-optimized JS isn't the only point of operation here. There's a LOT of exchange, parsing and processing that interacts with the File System and the JS engine itself. It isn't just a matter of loading a JS library and letting it do its thing. Every call that crosses the boundaries from JS runtime to the underlying host environment has a cost. This is multiplied across potentially many thousands of files.

Just going from ESLint to Biome is more than a 10x improvement... it's not just 1.5x because it's not just the runtime logic at play for build tools.


I'm not sure how it is in Construct, but IME "well-optimized" JavaScript quickly becomes very difficult to read, debug, and update, because you're relying heavily on runtime implementation quirks and micro-optimizations that make a hash of code cleanliness. Even you can hit close to native performance, the native equivalent usually has much more idiomatic code. The tsc team needs to balance performance of the compiler against keeping the codebase maintainable, which is especially vital for such a core piece of web infrastructure as TypeScript.


Are you comparing perfectly written JS to poorly written C++?


It sounds like the C++ is not well-optimized then?


Numeric code can, but compilers have to do a lot of string manipulation which is almost impossible to optimise well in JS.


How does that scale with number of threads?


Your JS code is way uglier than their Go code, if you're doing those kinds of shenanigans.

JS is 10x-100x slower than native languages (C++, Go, Rust, etc) if you write the code normally (i.e. don't go down the road of uglifying your JS code to the point where it's dramatically less pleasant to work with than the C++ code you're comparing to).


There's no such thing as a native language unless you're talking about machine code.

It's kind of annoying how even someone like Hejlsberg is throwing around words like "native" in such an ambiguous, sloppy, and prone-to-be-misleading way on a project like this.

"C++" isn't native. The object code that it gets compiled to, large parts of which are in the machine's native language, is.

Likewise "TypeScript" isn't non-native in any way that doesn't apply to any other language. The fact that tsc emits JS instead of the machine's native language is what makes TypeScript programs (like tsc itself) comparatively slow.

It's the compilers that are the important here, not the languages. (The fact that the TypeScript team was committed to making sure the typescript-go compiler is the same (nearly line-for-line equivalent) to the production version of the TypeScript compiler written in itself really highlights this.)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: