JITs are un-ergonomic
abe-winter.github.io
abe-winter.github.io
It started out great. The iteration cycles were incredibly short, performance was competitive, the code was portable, and we could do wild metaprogramming nonsense that was still fast. If you haven't worked with LuaJIT, its C FFI is also incredible!
As we started scaling though, the wheels fell off the wagon. We'd add a new feature and suddenly the game wouldn't run at interactive framerates. One time, it was the 'unpack' function, which would trigger a JIT trace abort. We would drop from 12ms frames to 100ms frames. I wrote a length-specialized version that didn't abort and moved on.
Another time, it was calling Lua's 'pairs' method (iterator over a map). Okay, so we can't do that, or a few other things that made Lua productive before.
The other problem we hit was GC predictability being impossible. We tried to mitigate it by using native data structures through the C FFI, taking control over the GC cycle to run it once or twice per frame, etc. In the end, like the JIT problem, we weren't writing Lua at the end, we were writing... something else. It wasn't maintainable.
That summer ruined dynamic languages for me. I didn't really want to be writing C or C++ at the time. I ended up picking up Rust, which was predictable and still felt high-level, and the Lua experience ended up getting me my current job.
I've hit fewer snags with LuaJIT, but they're definitely there. Really wish Mike Pall had written that hyperblock scheduler before retiring...
[1] https://github.com/LuaJIT/LuaJIT/tree/v2.1https://www.freelists.org/post/luajit/Looking-for-new-LuaJIT...
It's more of an emeritus situation than anything, he's still active on the list as well.
I'd be ecstatic if hyperblock scheduling, or the quad-color garbage collector, were to drop onto the 2.1 trunk, but I'm not counting on it.
Don’t build a datastore on the JVM if you care about tail latencies, you‘ll be fighting the GC forever (see Cassandra). Don’t rely on auto-vectorisation in your inner loop if possible, one tiny change could bring that house of cards crashing down.
I‘d be interested in how your team ended up picking that tech stack. Was it a „rational“ weighing of options with pros and cons? Was it „eh it‘ll be alright“? Was it personal preference and/or prior experience?
OpenJDK's ZGC [1] is well on its way to have worst-case latencies of under 1ms (as soon as this year) on heaps up to 16TB in size.
Also, 1ms is an eternity on modern hardware. That’s enough time to handle thousands of disk I/Os or network packets. That means each GC cycle can latency blip 1000’s of other machines.
Failed at what exactly? People are happy with what he have now, and generally happier with every release (there have been some issues around G1 vs. the now-defunct CMS collector in some special circumstances). Netflix, Amazon, Apple, Google, and Twitter run much or most of their infrastructure on OpenJDK, without even using the newest collectors (AFAIK).
> Also, 1ms is an eternity on modern hardware.
That depends if you're looking at latency or throughput. Java's GCs have achieved good throughput a long time ago. But ZGC isn't a throughput collector; it is intended for online applications that aren't CPU bound. If you're serving transactions and your application pauses for no more than 1ms every few seconds you're probably OK. There are kernel subsystems that may cause a similar or higher worst-case blip.
In any event, Java is not intended for those who must get 100% of the performance the hardware can support, and are willing to pay a high price for getting there, nor is it intended for specialized, niche usage, where perfect performance is easy to achieve. It is intended to get to 95-98% performance at the lowest effort, for a wide variety of applications, and its main runtime cost now is in memory footprint. It's not intended to replace C/C++; we've got Zig for that.
Also, 1ms is an eternity on modern hardware
I hate to break it to you but malloc and free aren't instant either. Also ZGC can do less than 1msec pauses, often way less. At some point it just doesn't matter anymore for virtually all users.
That means each GC cycle can latency blip 1000’s of other machines.
Linux kernel timeslice is longer than 1 msec, so good luck if your server runs >1 process of any kind at all.
I'm also planning to add support for tl, which should make things easier on the in-the-large engineering side of things - something dynamic languages are also pretty awful at.
[1]: https://docs.oracle.com/en/java/javase/14/vm/compiler-contro...
[2]: https://docs.oracle.com/en/java/javase/14/jfapi/why-use-jfr-...
[3]: https://docs.oracle.com/en/java/javase/14/docs/specs/man/jao...
[4]: http://psy-lob-saw.blogspot.com/2015/07/jmh-perfasm.html
[5]: https://www.graalvm.org/docs/reference-manual/native-image/
Structs have many limitations though and two different argument passing semantics in the same language mean more complexity. Also you may be lucky to eliminate allocations in your own code, but what about libraries? Existence of GC shepherds programmers and library creators into heap allocation. You don't need to write new explicitly to allocate on the heap.
Also high level languages with no GC make manual memory management much more usable and provide way better ergonomics in this area, just because they have to. E.g. automated reference counting, ownership/lifetime control, move semantics etc.
Automated reference counting is technically a GC, but a kind of GC that can be enabled only for a subset of objects. In languages which force GC on everything (even reference counted GC) the incentive to provide abstractions that work without GC are much weaker. It is just hard to opt-out from GC once you have it and once all your stdlib relies on it. I thing D learned it the hard way.
Besides, plenty of GC enabled languages offer the option to stack allocate, static global allocations, or native heap.
Some examples, including languages that for whatever reason failed on the mainstream market.
D, C#, Swift, Oberon, Oberon-2, Active Oberon, Component Pascal, Mesa/Cedar, Sing#, System C#, Nim, Modula-2+, Modula-3, VB, Xojo, C++ (via C++/CLI, C++/CX and Unreal C++), Common Lisp.
Go and .NET are doing quite alright.
This seems only useful if you want to use the VM, not if you want to embed it into your own application.
But the whole classpath exception clause is confusing to me (and from what I see I'm not the only one). I just want to write code, as opposed to interpreting licenses ... I.e., exactly why I would avoid this project, regardless of any good intentions from the side of the project team.
I agree the GPL+CE is a confusing license, however. But you're arguing that you should avoid Java because of the license. Think about all the companies that use it, when was the last time they had any issues because of the GPL?
The real problem with the GC is that it leads people to believe that they don't need to be considerate about how they are using memory.
You’re totally right that benchmarking and profiling is hard even for native code. I think this post fetishizes whether or not a piece of code got JITed a little too much. Maybe the author had a bad time with microbenchmarks. There’s this anti pattern in the JS world to extract a small code sample into a loop and see how fast it goes - something that C perf hackers usually know not to do. That tactic proves especially misleading in a JIT since JITs salivate at the sight of loops.
I know that compilers are smart, but the API doesn't make things easier at all
SomeClass x = new SomeClass();
won't make anything slower (ignoring auto for now)
Now, I read Java code and I can't help but wonder how things are harder
For example, setters and getters, what would be a simple memory write becomes a function call. Not complaining when you actually need it.
Reading the examples here (and those are not too bad) https://developer.android.com/reference/java/net/HttpURLConn... it seems you have to actually fight the API to get anything done
Why do you need to cast the return of a url.openConnection() to a HttpURLConnection? (I mean, how many connection types exist?)
Two? https://developer.android.com/reference/java/net/URLConnecti...
https://docs.oracle.com/en/java/javase/11/docs/api/java.net....
In general having some higher level, well optimized helpers can certainly reduce verbosity and increase speed. That said, some types of verbosity just make the programmer write what the compiler would translate to anyway. Or can end up being unnecessary - e.g. a strongly typed language with no inference certain slows the programmer with no effect on the program (though maybe an effect on the type checker).
rep movsb
will do the trick. This assumes that the source register points to source, destination register points to destination and counter register is set to number of bytes to copy.- A verbose SIMD copy loop is often faster even on modern Intel CPUs that have the more modern rep implementation.
- A simple byte copy loop will beat both SIMD and rep when you're copying smaller amounts at a time.
Still, the JVM JIT is faster than JS due to reasons explained in the sibling comment of the parent one.
You're thinking about generics. .class files preserve a whole bunch of type information (I'm building a .class decompiler in my free time, and I'm looking at that very same data in my debugger ATM).
Even generic types are available in the Java .class file and are accessible from the reflection API. Spring for example uses this quite heavily.
Type information is present in fields and class inheritance
For example, a class like this
`class Foo implements Bar<String>`
Retains the fact that the generic type is a String.
That information is completely lost at method invocation. So a method that takes a `Bar<String>` ultimately compiles to a method that takes a `Bar` and knows nothing of the String.
To get that generic information down you have to engage in some fun tricks using either the class or field method I mentioned earlier. (Usually you do this with a second type parameter where it matters).
In JS you can only get the types by profiling.
Also it’s not really true that genetics are erased. There’s that horrible thing javac does so that the VM can support reflection for generics. I get your point though.
Overall I think C# got this one right. Generics are right there in the binaries.
bUt ActuALLy, I think the rightest right thing is to do specialization up to representation at link time (or compile time), when the whole program is available, ala MLton. Virgil does this. Of course this is not possible in a dynamic code loading environment but I only have so many fucks to give in this life.
I agree C# got this right.
I agree that doing specialization up to link time is ideal from a certain standpoint.
e.g.
class A<Y extends X> {
// in this scope, Y is known to be of at least type X,
// so, we can call methods on expressions of type Y
// that belong to type X (and not Object)
Y m() { ... }
}
Type arguments are omitted from usage sites. The technique is literally called erasure in papers and documentation. a = new A<Foo>();
f = a.m(); // should return a Foo, in bytecode returns Y
// and compiler inserts a cast from Y -> Foo
Generic code is slower in Java because of these extra casts. To get back (most of) the performance, the JVM has to inline enough methods to be able to track the types from start to finish. It can't always.I don't know that the java JIT has type feedback. However regardless Java being statically typed means Java code is structurally closer to what a JIT wants and can analyse, it's much more difficult (and thus rarer) to fuck around with types and objects generated on the fly at runtime for instance, you're not going to add new attributes to an instance whereas that's just tuesday in javascript.
To whet your appetite, what happens if you redefine Math.round? Java prohibits this for obvious reasons, but a JS joker may write:
Math.round = () => Infinity;
JS engines really do inline Math.round, and must be prepared for this nonsense.It gets worse. Maybe you check for a property which is usually not found:
if (!val.uuid)
val.uuid = uuidgen();
Hidden classes can almost reduce this to a pointer comparison. But what if someone adds a uuid property on Object.prototype? Every check is busted! v8 handles this with "validity cells", and it's ugly, requiring that every object know about every object which "inherits" from it.Now if you are a monster you may choose to write:
Object.defineProperty(Array.prototype, "42", {value: "lol"});
console.log([][42]); // yes it prints "lol"
Every array gets a default value for 42. Think about how you would JIT numeric code in such an environment...There's also the "frame evaluation API" PEP [1], whose purpose is to allow pluggable evaluators in CPython without forking the entire interpreter, like Unladen Swallow had to.
Not as straightforward as with JS, but Java JIT still have to account for those things.
May be it will assume some things about standard classes for optimization, but for user classes that will be true anyway.
Yeah, that is what they meant, but it is a little misleading. Javascript's performance is usually within a single digit multiple of java's, whereas python is often significantly slower. Javascript is somewhere between Java and an abacus too.
(Broken ecosystem = lots of things don’t work with lots of other things; package managers are another example; Mypy another)
E.g. pg8000 looks like it's being maintained?
Similarly, Postgres doesn't work for Pypy unless you're willing to use libraries that are unmaintained or only very lightly maintained, and even then who knows what the performance tradeoffs will be. Those are pretty substantial caveats for a production use case.
I haven't used Poetry, but I've heard good things. I'm very cautious though, because I've also heard high praise about pip/virtualenv, pip-tools, pipenv, etc and every single one had spectacular problems on the happy path (30 minutes to resolve dependencies with pipenv is my favorite).
Sure that's true, but performance is about measurement.
Poetry has some missing pieces still (I can list them if you're interested but I'm on my phone) but it's the first time I've felt ok recommending a packaging tool to beginners. The experience is way better than the alternatives like pipenv.
I have a very low degree of confidence that a pure Python solution (even JIT optimized) is going to compete with a native solution, not to mention that by all appearances it isn't sufficiently well-supported to meet our criteria. Measuring the performance for me feels like a waste of time; it's easier just to forego Pypy until the ecosystem matures.
> Poetry has some missing pieces still (I can list them if you're interested but I'm on my phone) but it's the first time I've felt ok recommending a packaging tool to beginners. The experience is way better than the alternatives like pipenv.
This is good to know. I'll have to give it a try.
You are correct that JS is not between Python and Java. Python is faster than Java which is faster than JS. Though some people seem to think calling APIs written in C is "not Python" but if the ecosystem provides the library and I call it from Python then it's Python enough for me!
Get used to this approach since it crops up everywhere. If you target a gpu, you try to keep the work in the gpu. When we move to use bpf and io_uring things will be a race to the next kernel program. If you target the CPU then your code is a race to C or a race to the function that calls the relevant cpu instruction that will make your code fly.
In JavaScript, it's a black box. I know some constructs might deoptimize functions when run on Wednesdays because I read them on a blog published in 2018 that's _probably_ still accurate. In my benchmark running on Node 12.14.1 on Windows this seems to be true. But then who knows if it'll be the same thing in production, and it might 'silently' change later on.
JavaScript in V8 is incredibly fast these days, but I find it much easier to write optimal code in Go.
Why doesn't the optimizer generate different compiled implementations of a function for different pieces of code?
Go’s compiler is kind of stupid in this sense.
You can actually look at the assembly generated by V8 and you can trace de-opts. The workflow is just so awful, you're not going to want to do it.
Sorry, and I may be oversimplifying the author's situation, but this really sounds like a case where you need to not be using JS for your server. On the client you don't have much choice, but on the client the pure-JS performance rarely gets tight enough to warrant this degree of micro-optimization work.
Author makes some good points - it would be great if the JIT were more profiler-friendly - but I have to question a little bit how important it actually is, the way the use-cases line up.
- Piles of ads/analytics scripts which have no motivation not to slow down the page
- Reflow; i.e. needlessly many elements on the page causing the browser to do extra work calculating layout
- The initial loading and JIT-ing time of a needlessly heavy JS bundle
Byte code interpreters do that? That would be surprising. Programs are represented with 'lines' in byte code?
Things he wants, let's look at SBCL, a Common Lisp implementation (see http://sbcl.org ):
> compile short bits to native code quickly at runtime -> that's done by default
> identify whether a section is getting optimized -> we tell the compiler and the compiler gives us feedback on the optimizations performed or missed
> know anything about the native code that’s being run in a benchmark -> we can disassemble it, some information can be asked
> statically require that a given section can & does get optimized -> done via declarations and compilation qualities
> compile likely sections in advance to skip warmup -> by default
> bonus: ship binaries with fully-compiled programs -> dump the data (which includes the compiled code) to an executable
kdb+/q has a bytecode compiler and each line is turned into bytecode and run, line by line. Also the performance is as good as C, most of the time.
A JS frontend for SBCL could be nice...
Why should we need to hand compile special versions of JavaScript VMs, just to get (decompile ....)?
Is this a real problem? I’ve been profiling my JS for years and never actually run into a mysterious problem where some important code I profiled was way way slower in prod than when I was profiling. Has that happened for you? How often does this happen? I take it as an assumption that profiling is something you mostly do on inner loops & hot paths in the first place. I mean, I profile everything to look for bottlenecks, but I don’t spend much time optimizing the cold paths.
> Get notified about deopts in hot paths
Studying the reasons for de-opts help you know in advance when it might happen. If you avoid those things, do-opts won’t happen, and you don’t need notifications.
For example, ensure you don’t modify/add/delete keys in any objects, make sure your objects are all the same shape in your hot path, don’t change the type of any properties, and you’re like 90% there, right?
> statically require that a given section can & does get optimized [...] compile likely sections in advance to skip warmup
While these don’t exist in V8, it’s maybe worth mentioning that the Google Closure compiler does help a little bit, it ensures class properties are present and initialized, which can help avoid de-opts.
You do have to change how you think about performance analysis, but in return, you get to actually answer the question you're trying to reason about, namely, how does this run in production.
Pacifying the JIT is a bit of a dark art, but the whole thing is pretty transparent with good tooling. I've yet to regret building on LuaJIT.
But the alternative is bad performance all the time (the JITS fall back to interpretation, after all).
What's the value in having clearly understood bad performance? If you care enough about performance that you need to understand it, surely you care about the absolute level of performance.
Also in many cases having it's much better to trade off average performance for lower variance.
I think AOT/JIT as a dichotomy is stupid, for what it's worth. Why shouldn't I have e.g. both profile guided optimization and JIT in the same program?
You can have both on the same toolchain, and plenty language do have them.
However in some scenarios, like constrained embedded devices, you are left with AOT only.
By the way, modern Android is all three, interpreter, AOT and JIT with PGO.
If you write a web app in Ruby using a similarly light weight frameworks as you would using Go, you'll be lucky to get 3x the throughput out of the Go app.
There are also ways to more efficiently utilize hardware by using bytecode than distributing binaries. Using bytecode verification rather than an opaque binary, you can pack thousands of different web applications into a single process.
This is an application of webassembly which IMHO is much more promising than in-browser use.
> Using bytecode verification rather than an opaque binary, you can pack thousands of different web applications into a single process
Can you expand on what problem this solves?
Anyway, the problem isn't the bytecode, it's the shitty interpreter. There are plenty of bytecode based languages that are within < 10x of C.
People get bogged down. They don't write low allocation code. Nobody has any time to turn on the compiler warnings for escape analysis. Your benchmarks end up a year out of date and don't even run anymore. Architecture becomes an afterthought. Function/method calls end up becoming even slower gRPC calls as the team struggles to box up complexity and extracts more services.
WRT bytecode verification it's better just to read this: https://www.fastly.com/blog/announcing-lucet-fastly-native-w...
There's nothing shitty about the CRuby bytecode VM. It's all a question of resourcing. You're doing a huge disservice to all the people who worked very hard to make CRuby 2.7 5-15x faster than 1.8 and it detracts from a valid discussion about reducing the CO2 footprint of datacenters around the world.
The alternative is to leave JavaScript to the browser, where it belongs.
Back to 2000's, ou had several commercial players, the most well known ones were Aicas, PTC, IBM, Aonix, J/Rockit, ...
In what concerns Android, it went to a couple of ways, in Android 5 introduced AOT compilation, but it did not scale to do that on device, when multiple applications where installed, with Android 7 Google introduced another approach.
Basically a mix of interpreted using a hand writter interpreter in Assembly, followed by a JIT, and then AOT compiling with PGO information gathered from the JIT, https://source.android.com/devices/tech/dalvik/jit-compiler
There are also a couple of Google IO related talks.
Back to standard Java implementations, not only you have those commercial vendors, IBM and Oracle have finally made AOT a free feature so you get it on OpenJDK and OpenJ9.
Besides AOT compiling, you also have the possibility to use a JIT cache between execution runs instead. This is sometimes a better approach, because you cannot use AOT on code that depends on reflection, unless you tell the compiler about all the classes that it doesn't see, but are also required to land on the executable.
Most well known Java related conferences (JaX, Devoxx, NDC, Java ONE, CodeOne, JVM Language Summit) have a couple of AOT related talks every now and then.
And that’s where you’ve lost me. Not sure how you expose anything about how the JIT is operating without introducing a compat shitshow since JIT means different things in different implementations.
If you really want to know that something gets compiled with the types you want, use a statically typed and ahead of time compiled language.
If you have to use a JIT but you find that it doesn’t do what you like then remember that it’s meant to be stochastic. The VM is just trying to win in the average. Which functions get compiled and with what type information can vary from run to run.
Probably the best thing that could happen is that developer tools tell you more about what the JIT is doing. But even that’s hard.
There are some specifics that I disagree with:
- I don’t think all JIT architects for JS claim that the perf is about competing with C for numerical code. I don’t explain it that way. I would say: JITs are about doing the best job you can do under the circumstances. They can make JS run 4x faster than an interpreter if things really go well. “Between Python and Java” is a good way to put it and that’s exactly what I would expect. So if that’s your experience then great! The JIT worked as expected.
- It’s usually foolish to want your code compiled sooner. Compilation delay is about establishing confidence in profiling. I’m pretty sure we’d JIT much sooner if it wasn’t for the fact that it would make the EV of our speculation go negative.
TL;DR. the JIT can’t unfuck up JavaScript.
Not to say server-side JavaScript is necessarily a good idea. But if people are determined to make it work, I could see these sorts of changes happening that wouldn't make sense in a web browser.
Why? You have the exact same problem when writing SQL code but there you have lots of powerful introspection tools to make it easier to control performance. You can also use indices and hints to nudge the RDBMS into executing queries in the most optimal way. PyPy for example has a lot of support for introspection.
The nondeterminism can come from lots of places. In JSC it’s that the JIT polls the heap for some of its profiling and it profiles concurrently. So OS scheduling decisions affect what types the JIT sees.
All all JavaScript JITs this way?
In all seriousness, I think I heard that FF doesn’t profile concurrently. Maybe that’s one of the reasons why they’re so slow.
To have useful performance measurement tools that are going to help your code run across multiple implementations would require a) all of those implementations to work the same, forever, and b) the semantics of this to be specified in the standard.
When you have workloads that are mostly io bound, having syntactic sugar like js async/await to avoid blocking is really a huge strength.
When we write systems, we operate under constraints, and frequently the largest constraint is time to market rather than pure performance.
Dynamic typing can be a huge strength for TTM.
If performance was the only concern, we'd all be in straight C with inline asm.
Structural typing fit really nicely with existing JS. Typescript have all expressiveness of JS and granular control of type safety. Given this, there is a lot of sharp edges.
`main` turns into[1]:
public static void main(String[] args) {
}
Because of this and Lombok I don't find Java particularly taxing to write. I often prefer writing Java as my IDE can provide me better information about the code.
[1] https://www.jetbrains.com/help/idea/using-live-templates.htm...
Features I care about:
* Type classes: Haskell, Scala
* Module system: OCaml, Scala
* structural subtyping / row polymorphism: OCaml
* Higher-kinded types: Haskell, Scala
Other important features, but not related to the type system:
* Powerful runtime system (multicore support, green threads, ...): Haskel, Scala (kinda, with the TypeLevel libraries, but still not 1st class)
* pure functional programming focus: Haskell, Scala (kinda, with the TypeLevel libraries, but still not 1st class)
* compiling to JavaScript (so that you can use 1 language for both backend and frontend): Scala, OCaml
... maybe PureScript would tick the most boxes ¯\_(ツ)_/¯
Besides, most JavaScript programmers are already dependent on Microsoft products like TypeScript, NPM, and Visual Studio Code. What's one more?
The debugger (clrdbg) is still artificially restricted from running inside anything else than MS' IDEs.
The global certificate store is still a thing.
On that note, the cryptography support varies wildly between platforms, because they still insist on using SChannel on Windows. And I guess that means that they have to use SecureTransport on macOS, to avoid appearing to play favourites or something.
The action at a distance that it encouragtes would make you think you're in a haunted house.
Despite bringing the Windows mentality with them everywhere, there still isn't a competent migration story from Framework, so what's the point? Legacy code is still a pain to port, and you'd be crazy to actually want to write new code in this mess.
It is still not in the same league as Java when it comes to dev tools and the Java ecosystem keeps getting better as well, but at the moment I am torn between the two (or rather, I have two excellent options that I can choose depending on circumstances.)
.NET Core is nice enough to do very large projects in though so I cannot see many reasons of moving away from it (especially from F# which I mostly use now).
It's also not exactly run like a community project. The issue tracker feels more like a customer support system: "can you try updating to the newest release now?" abound, with no mention of what the actual problem or fix was.
From where I am standing it has always been either Java or .NET since the last 20 years, while we keep seeing stuff coming and going every couple of years.
I find a lot of joy in working in C# and the .NET core. It's very pleasant to work in and might be worth a look.
https://dotnet.microsoft.com/download/dotnet-core/3.1
I even managed to get it to run on an Android tablet via Termux by downloading the ARM64 binaries.
To which my shortlist is Haskell, C#, and Rust.
After that, you have Go at 9%, Kotlin (also a JVM Lang) at 6.6% and Scala (also a JVM Lang) at 4%.
Finally, you get Elixir and Clojure both at 1.5%
Source: https://insights.stackoverflow.com/survey/2019
So yes, I think learning Java and the JVM ecosystem is a good investment, considering Java dominates, and if you tally up JVM langs you get something like 51% of professional developers using a JVM based language for work.
We've drifted from just compiled languages here, but the most important server-side language to know is probably PHP. Personally I've really enjoyed working with Drupal.
Also, that doesn't look like a great metric, since it's just the number of website using a particular technology. That's going to be massively skewed by prefab solutions like WordPress and all.
I liked that the StackOverFlow survey asked people what language they used professionally. I feel that's much more relevant.
I've never seen PHP being used for those usages. So I probably made an assumption here that it isn't, or rarely is.
All the languages I listed generally run their own server, and can handle the full request, down to its connection. While I remember PHP only managing the payloads from an existing server, running as an embedded script which the server delegated too on the last step for final rendering of the response.
So for example, it couldn't manage any type of long running in-memory process or anything like that. And it didn't have direct access to IO, as that's all managed by the containing server. Things like that, which not all REST APIs would need, but is generally something I'd expect to be able to do for more generic backend work. Don't know if there are now ways to do all this.
(I'm not convinced it's long term profitable to work on boring platforms either, but of course looking for fun things is more risky)
Doesn't seem that bad to be invested into Java/.NET/C++/Web eco-systems.
[1]: https://www.infoq.com/presentations/java-robot-swarms/
[2]: https://en.wikipedia.org/wiki/Liquid_Robotics
[3]: http://lamport.azurewebsites.net/tla/tla.html
[5]: https://www.infoq.com/presentations/java-science-aerospace/
There have been attempts at AOT compiling Java, but afaict they have very little usage. GCJ, now dead, was the main one for years. There's also a newer experimental compiler jaotc: https://openjdk.java.net/jeps/295.
It's difficult for JS engines to match Java's performance without type information to work with. This is only discovered at runtime and the JIT'ed code for a function requires traps in case it is later called by parameters of a different type.
Outside Android, it has always been available in commercial JDKs since the early 2000's.
GCJ was never the main one, it never went beyond a toy AOT compiler.
Anyone that cared about AOT compiling in Java would be paying big bucks to Excelsior JET, Aicas, Aonix, PTC, IBM, J/Rockit, Gemalto and a couple of smaller players.
Naturally in 20 years quite a few things changed, and now PTC, Aicas, IBM, Gemalto are the survivors, while MaximeVM graduated into GraalVM and there is a free beer layer for everyone not willing to pay for AOT compilers in Java.
But in general, AoT is usually a win, even though it doesn't have access to runtime data. C and Fortran are still the gold standard for performance, for example. One reason is that JITs are time constrained, even in long-running processes. Another is these languages are designed for AoT compilation.
And there are ways for AoT compilation to have some, albeit limited, access to runtime data. One way is 'profile guided optimisation'. Another is inserting probes for different types of CPU.
There are plenty of Java implementations to choose from.
I wish professors would actually spend more than 5 minutes discussing how languages and implementations aren't the same thing.
In the end it is all bytecode (so java, or rather both java and kotlin output bytecode), but kt removes a lot of the ceremony, boilerplate, and altogether ideas that have proven to do not be that great in the years since java's inception.
E.G. I strongly prefer data class to records. No named parameters -> no default params -> way more boilerplate
However, I still use C++ for extremely complex networked applications where performance matters, unfortunately Rust doesn’t have battle tested crypto libraries, so I wouldn’t personally use it for networked applications.
Code generation isn't so bad. Even JOOQ (Java's awesome ORM) will suggest you do it. And similarly, Java's generics aren't that great themselves. But you come to expect more from a newer language with so much history to learn from and they missed the ball on generics. (In contrast they got right: gofmt, go test, cross platform builds, go get, etc.)
There are newer languages that are more interesting, but picking one of those is more about either attempting to anticipate where the industry is going, or just plain wanting a more fun language. Rust is an example here. Personally I think rust is worth learning, and has a fair shot at taking over a large chunk of software development that would have previously gone to C or C++. Learning rust might make you a better programmer because lots of poor-programming-hygiene type things are compiler errors in rust.
If you just want a way to wring maximum performance out of a piece of hardware, then I would caution you to consider your choice carefully. Writing high performance code requires a complete understanding of the stack underneath you. If you want to see my point in practice go check out the programming languages shootout. Specifically notice that there are benchmarks implemented in "fast" languages that are outperformed by better optimized code written in "slow" languages.
I would recommend against C++ unless you are planning on devoting a very large amount of time to learning it well. I also don't personally like C++. Too many footguns.
I haven't observed that, at least not where one can't write a "better optimized" code in the "fast" language too. Can you give any specific examples?
1. https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
So in this case it's less "an algorithm" in the narrow sense and more "is it written to use all CPU cores effectively" and is it using the JIT to run the code compiled to the native. Other languages with good scores are also all that can do a good JIT and use all the CPU cores, which yes, confirms that good JITs are really good.
Which still also doesn't mean that I don't know (or have happened to know) enough incompetent programmers who would, indeed, make even worse performing C++ than any entry that we see in the Benchmarks game. I could write a whole article about all the things they would or could do wrong.
I only wanted to see the examples in "the Benchmarks game" specifically.
I'm not sure what you are trying to say though? My point was that you have to develop expertise before you can create high performance programs. If you are considering dropping node because you need more performance, be careful.
That's what is surely not surprising to me, as I've measured the performance of V8 even before node.js existed, and already knew of enough examples that resulted in the code executed with the speed comparable to C. I don't consider any modern JavaScript JIT implementation to be "slow", neither V8, nor the one in Safari or in Firefox. Moreover, all of them have more than one level of achieving the speed -- they adapt to the needs of the code executed, they are really the works of art, and not an accidental development.
If you like books, this should be a good intro: https://underscore.io/books/essential-scala/ (you should use IntelliJ IDEA instead of Eclipse, though)
And you can also use it for frontend development: https://www.scala-js.org/doc/sjs-for-js/es6-to-scala-part1.h... (having one language for both backend and frontend leads to great synergy)
Microbenchmarks don't represent the real world. Instead of running a single microbenchmark, I suggest running a host of them, but all of them "at the same time" (not serially). They'll get in each others ways and total performance will be far worse.
This happens with AOT code as well of course, because caches are being trampled no matter what, but JIT code only exarcerbates the issue because it is larger.
…not really? Managed languages with a substantial runtime tend to make for poor ergonomics when writing JITs.