Hacker News new | ask | show | jobs
by procaryote 246 days ago
Hello world in java is pretty fast. Not rust fast but a lot faster than you'd expect.

Java starting slowly is mostly from all the cruft in the typical java app, with springboot, dependency injection frameworks, registries etc. You don't have to have those, it's just that most java devs use them and can't conceive of a world of low dependencies

Still not great for commandline apps, but java itself is much better than java devs

2 comments

Testing on my machine, Hello World in java (openjdk 21) takes about 30ms.

In contrast, "time" reports that rust takes 1ms, which is the limit of it's precision.

Python does Hello World in just 8ms, despite not having a separate AOT compilation step.

The general guidance I've seen for interaction is that things start to feel laggy at 100ms; so 30ms isn't a dealbreaker, but throwing a third of your time budget at the baseline runtime cost is a pretty steep ask.

If you want to use the application as a short lived component in a larger system, than 30ms on every invocation can be a massive cost.

App that actually does something will probably have even larger startup overhead in Java as there will be more to compile just-in-time.
Only when not using either AOT or JIT cache.
I recall that Mercurial was really fighting their Python test harness. It essentially would startup a new Python process for each test. At 10ms per, it added up to something significant, given their volume of work to cover something as complicated as SCM.
10ms?

Did they have like 100k tests?

Found a 2014 thread where 10-18% of the test harness time was spent booting the interpreter for the 13,000 required instances. The deeper thread was showing some 500-700 seconds of test time was just the interpreter overhead. The original point of the article was how much worse the overhead was in Python3 vs Python2.

https://mail.python.org/pipermail/python-dev/2014-May/134528...

That's a lot more extreme than 10ms and yea I get your point better now
I'm trying and failing to imagine a situation where 30ms startup time would be a problem. Maybe some kind of network service that needs to execute a separate process on every request?
30ms is pretty close to noticeable for anything that responds to user input. 30ms startup + 20-70ms processing would probably bump you into the noticeable latency range.
People play midi keyboards with 30 ms latency.
Yeah I don’t think 30ms is very noticeable, but say you have a cli tool with other 20-70ms to bump you up to 50-100ms your tool will have noticeable latency. Death by a thousand cuts.
30ms is the absolute best case. Throw some spring in there and you're very quickly at 10s. rub some spring-soap and it's near enough to 60s
And imagine if you start adding sleep calls! Those could take minutes to hours, or even days!
New HN submission: How I Made My Sleep Function Accidentally Quadratic.
It's not about how long someone is willing to wait with a timer and judge it on human timescales, it's about what is an appropriate length of time for the task.

30ms for a program to start, print hello world, and terminate on a modern computer is batshit insane, and it's crazy how many programmers have completely lost sight of even the principle of this.

Java is a tool, a very good one.
Java's biggest weakness in this area is its lack of value types. It's well known, Project Valhalla has been trying to fix it for years, but the JVM just wasn't built around such types and it's hard to bolt them on after the fact. Java's next biggest weakness (which will become more evident with value types) is its type-erased generics. Both of these problems lead to time wasted on unnecessary GC, and though they can be worked around with arrays and codegen, it's unwieldy to say the least.
Project Valhalla will also specialise generics for value types. When you say, "it's hard to bolt on", the challenge isn't technical, but how to do this in a way that adds minimal language complexity (i.e. less than in other languages with explicit "boxed" and "inlined" values). Ideally, this should be done in a way that tells the compiler know which types can be inlined (e.g. they don't require identity) and then letting the compiler decide when it wants to actually inline an instance as a transparent optimisation. The challenge would not have been any smaller had Java done this from the beginning.
Maybe I picked the wrong wording--I don't mean to diminish the ambitions or scope of Valhalla--but I definitely think the decision to eschew value types at the start has immense bearing on the difficulty of adding them now.

Java's major competitors, C# and Go, both have had value types since day one and reified generics since they gained generics; this hasn't posed any major problems to either language (with the former being IMO already more complex than Java, but the latter being similarly or even less complex than Java).

If the technical side isn't that hard, I'd have expected the JVM to have implemented value types already, making it available to other less conservative languages like Kotlin, while work on smoothly integrating it in Java took as long as needed. Project Valhalla is over a decade old, and it still hasn't delivered, or even seems close to delivering, its primary goals yet.

Just to be clear, I don't think every language needs to meet every need. The lack of value types is not a critical flaw of Java in general, as it only really matters when trying to use Java for certain purposes. After all, C# is very well suited to this niche; Java doesn't have to fit in it too.

> Java's major competitors, C# and Go, both have had value types since day one

Yes (well, structs; not really value types), but at a significant cost to FFI and/or GC and/or user-mode threads (due to pointers into the stack and/or middle of objects). Java would not have implemented value types in this way, and doing it the way we want to would have been equally tricky had it been done in Java 1.0. Reified generics also come at a high price, that of baking the language's variance strategy into the ABI (or VM, if you want). However, value types will be invariant (or possibly extensible in some different way), so it would be possible to specialise generics for them without necessarily baking the Java language's variance model into the JVM (as the C# variance model is baked into the CLR).

Also, C# and Go didn't have as much of a choice, as their optimising compilers and GCs aren't as sophisticated as Java's (e.g. Java doesn't actually allocate every `new` object on the heap). Java has long tried to keep the language as simple as possible, and have very advanced compilers and GCs.

> If the technical side isn't that hard, I'd have expected the JVM to have implemented value types already, making it available to other less conservative languages like Kotlin, while work on smoothly integrating it in Java took as long as needed

First, that's not how we do things. Users of all alternative Java Platform languages (aka alternative JVM languages) combined make up less than 10% of all Java platform users. We work on the language, VM, and standard library all together (this isn't the approach taken by .NET, BTW). We did deliver invokedynamic before it was used by the Java language, but 1. that was after we knew how the language would use it, and 2. that was at a time when the JDK's release model was much less flexible.

Second, even if we wanted to work in this way, it wouldn't have mattered here. Other Java Platform languages don't just use the JVM. They make extensive use of the standard library and observability tooling. Until those are modified to account for value types, just a JVM change would be of little use to those languages. The JVM comprises maybe 25% of the JDK, while Kotlin, for example, makes use of over 95% of the JDK.

Anyway, Project Valhalla has taken a very long time, but it's making good progress, and we hope to deliver some of its pieces soon enough.

Go I agree, .NET is on par with JVM, even if they don't have the pleothora of choice regarding JVM implementations, and the ability to do C++ like coding means there isn't that much of a pressure for pauseless GC as in Java.

Looking forward to Project Valhalla updates, I had some fun with the first EA.

I'm not sure what is meant here by "on par with the JVM." I'm not trying to claim that one or the other is better, but there is a basic difference in how they're designed and continue to evolve. .NET believes in a language that gives more control on top of a more basic runtime, while Java believes in a language that's smaller built on top of a more advanced runtime. They just make different tradeoffs. .NET doesn't "need" a more advanced runtime because limitations in its runtime can be overcome by more explicit control in the language; Java doesn't "need" a more elaborate language because limitations in the level of control offered by the language can be overcome by a more sophisticated runtime.

I'm not saying these are huge differences, but they're real. C# has more features than the Java language, while Java's compiler and GCs are more sophisticated than the CLR's. Both of these differences are due to conscious choices made by both teams, and they each have their pros and cons. I think these differences are very apparent in how these two platforms tackled high-scale concurrency: .NET did it in the language; Java did it in the runtime. When it comes to value types, we see a similar difference: in C# you have classes and structs (with autoboxing); in Java we'll just have classes that declare whether they care about identity, and the runtime will then choose how to represent each instance in memory (earlier designs did explore "structs with autoboxing" but things have moved on from there, to the point of redefining autoboxing even for Java primitives; for a type that doesn't care about identity, autoboxing becomes an implementation detail - transparently made by the compiler - with no semantic difference, as a pointer or a value cannot be distinguidhed in such a case - hence https://openjdk.org/jeps/390 - unlike before, when an Integer instance could be distinguished from an int).

This has been a very informative discussion, so thank you for that.

I did want to point out, though, that both languages have other (compound) value types than just structs. Go has value-based arrays, and C# has value-based tuples. It looks like C# may gain value-based discriminated unions as well, though it's not settled yet: https://github.com/dotnet/csharplang/blob/main/proposals/uni...

Currently it takes lots of boilerplate code, however with Project Panama API you can model C types in memory, thus kind of already using value types even if Valhala isn't yet here.

To avoid manually writing all the Panama boilerplate, you can instead write a C header file with the desired types, and then run jextract through it.