logoalt Hacker News

Java is fast, code might not be

101 pointsby siegerstoday at 1:43 PM106 commentsview on HN

Comments

liampullestoday at 2:54 PM

Understanding algorithmic complexity (in particular, avoiding rework in loops), is useful in any language, and is sage advice.

In practice though, for most enterprise web services, a lot of real world performance comes down to how efficiently you are calling external services (including the database). Just converting a loop of queries into bulk ones can help loads (and then tweaking the query to make good use of indexes, doing upserts, removing unneeded data, etc.)

I'm hopeful that improvements in LLMs mean we can ditch ORMs (under the guise that they are quicker to write queries and the inbetween mapping code with) and instead make good use of SQL to harness the powers that modern databases provide.

show 5 replies
cmovqtoday at 3:56 PM

When you're using a programming language that naturally steers you to write slow code you can't only blame the programmer.

I was listening to someone say they write fast code in Java by avoiding allocations with a PoolAllocator that would "cache" small objects with poolAllocator.alloc(), poolAllocator.release(). So just manual memory management with extra steps. At that point why not use a better language for the task?

show 3 replies
spankaleetoday at 4:37 PM

Avoiding Java's string footguns is an interesting problem in programming languages design.

The String.format() problem is most immediately a bad compiler and bad implementation, IMO. It's not difficult to special-case literal strings as the first argument, do parsing at compile time, and pass in a structured representation. The method could also do runtime caching. Even a very small LRU cache would fix a lot of common cases. At the very least they should let you make a formatter from a specific format string and reuse it, like you can with regexes, to explicitly opt into better performance.

But ultimately the string templates proposal should come back and fix this at the language level. Better syntax and guaranteed compile-time construction of the template. The language should help the developer do the fast thing.

String concatenation is a little trickier. In a JIT'ed language you have a lot of options for making a hierarchy of string implementations that optimize different usage patterns, and still be fast - and what you really want for concatenation is a RopeString, like JS VMs have, that simply references the other strings. The issue is that you don't want virtual calls for hot-path string method calls.

Java chose a single final class so all calls are direct. But they should have been able to have a very small sealed class hierarchy where most methods are final and directly callable, and the virtual methods for accessing storage are devirtualized in optimized methods that only ever see one or two classes through a call site.

To me, that's a small complexity cost to make common string patterns fast, instead of requiring StringBuilder.

show 2 replies
cogman10today at 4:18 PM

Nitpick just because.

Orders by hour could be made faster. The issue with it is it's using a map when an array works both faster and just fine.

On top of that, the map boxes the "hour" which is undesirable.

This is how I'd write it

    long[] ordersByHour = new long[24];
    var deafultTimezone = ZoneId.systemDefault();
    for (Order order : orders) {
        int hour = order.timestamp().atZone(deafultTimezone).getHour();
        ordersByHour[hour]++;
    }
If you know the bound of an array, it's not large, and you are directly indexing in it, you really can't do any better performance wise.

It's also not less readable, just less familiar as Java devs don't tend to use arrays that much.

show 2 replies
kyrratoday at 2:41 PM

First request latency also can really suck in Java before hotpathed code gets through the C2 compiler. You can warm up hotpaths by running that code during startup, but it's really annoying having to do that. Using C++, Go, or Rust gets you around that problem without having to jump through the hoops of code path warmup.

I wish Java had a proper compiler.

show 10 replies
sgbealtoday at 5:33 PM

Slight correction:

> StringBuilder works off a single mutable character buffer. One allocation.

It's one allocation to instantiate the builder and _any_ number of allocations after that (noting that it's optimized to reduce allocations, so it's not allocating on every append() unless they're huge).

Okxtoday at 4:29 PM

The code:

  public int parseOrDefault(String value, int defaultValue) {
      if (value == null || value.isBlank()) return defaultValue;
      for (int i = 0; i < value.length(); i++) {
          char c = value.charAt(i);
          if (i == 0 && c == '-') continue;
          if (!Character.isDigit(c)) return defaultValue;
      }
      return Integer.parseInt(value);
  }
Is probably worse than Integer.parseInt alone, since it can still throw NumberFormatExceptions for values that overflow (which is no longer handled!). Would maybe fix that. Unfortunately this is a major flaw in the Java standard library; parsing numbers shouldn't throw expensive exceptions.
wood_spirittoday at 3:14 PM

A subject close to my heart, I write a lot of heavily optimised code including a lot of hot data pipelines in Java.

And aside from algorithms, it usually comes down to avoiding memory allocations.

I have my go-to zero-alloc grpc and parquet and json and time libs etc and they make everything fast.

It’s mostly how idiomatic Java uses objects for everything that makes it slow overall.

But eventually after making a JVM app that keeps data in something like data frames etc and feels a long way from J2EE beans you can finally bump up against the limits that only c/c++/rust/etc can get you past.

show 2 replies
kpw94today at 4:15 PM

The Autoboxing example imo is a case of "Java isn't so fast". Why can't this be optimized behind the scenes by the compiler ?

Rest of advice is great: things compilers can't really catch but a good code reviewer should point out.

show 2 replies
Izkatatoday at 4:45 PM

"Java is slow" is a reputation it earned in the 90s/2000s because the JVM startup (at least on Windows) was extremely slow, like several seconds, with a Java-branded splash screen during that time. Even non-technical people made the association.

titzertoday at 3:31 PM

For fillInStackTrace, another trick is to define your own Exception subclass and override the method to be empty. I learned this trick 15+ years ago.

It doesn't excuse the "use exceptions for control flow" anti-pattern, but it is a quick patch.

urauratoday at 4:39 PM

I thought those were common sense until I worked on a program written by my colleague recently.

jerftoday at 4:27 PM

Any non-trivial program that has never had an optimizer run on it has a minimal-effort 50+% speedup in it.

hiyertoday at 3:50 PM

I ran into 5 and 7 in a Flink app recently - was parsing a timestamp as a number first and then falling back to iso8601 string, which is what it was. The flamegraph showed 10% for the exception handling bit. While fixing that, also found repeated creation of datetimeformatter. Both were not in loops, but both were being done for every event, for 10s of 1000s of events every second.

show 1 reply
zvqcMMV6Zcrtoday at 3:12 PM

> Exceptions for Control Flow

This one is so prevalent that JVM has an optimization where it gives up on filling stack for exception, if it was thrown over and over in exact same place.

show 2 replies
comrade1234today at 3:05 PM

Also finding the right garbage collector and settings that works best for your project can help a lot.

taspeotistoday at 3:26 PM

Knock Knock

Who’s there?

long pause

Java

latchkeytoday at 5:22 PM

When they say that AI will replace programmers, I think of this article and come to terms with my own job security.

Most of this stuff is just central knowledge of the language that you pick up over time. Certainly, AI can also pick this stuff up instantly, but will it always pick the most efficient path when generating code for you?

Probably not, until we get benchmarks into the hot path of our test suite. That is something someone should work on.

ww520today at 3:58 PM

The autoboxing in a loop case can be handled by the compiler.

jandrewrogerstoday at 3:01 PM

You can write many of the bad examples in the article in any language. It is just far more common to see them in Java code than some other languages.

Java is only fast-ish even on its best day. The more typical performance is much worse because the culture around the language usually doesn't consider performance or efficiency to be a priority. Historically it was even a bit hostile to it.

show 2 replies
bearjawstoday at 2:34 PM

JavaScript can be fast too, it's just the ecosystem and decisions devs make that slow it down.

Same for Java, I have yet to in my entire career see enterprise Java be performant and not memory intensive.

At the end of the day, if you care about performance at the app layer, you will use a language better suited to that.

show 3 replies
victor106today at 3:29 PM

this is great, so practical!!!

any other resources like this?

spwa4today at 4:53 PM

Java IS fast. The time between deciding to use Java and Oracle's lawyers breaking down your door is measured in just weeks these days.

null-phnixtoday at 2:56 PM

[dead]

ryguztoday at 2:52 PM

[dead]

andrewmcwatterstoday at 2:51 PM

[dead]

r_leetoday at 2:30 PM

[flagged]

tripple6today at 2:41 PM

Do good, don't do bad. Okay.

show 1 reply
koakuma-chantoday at 2:28 PM

As much as I love Java, everybody should just be using Rust. That way you are actually in control, know what's going on, etc. Another reason specifically against Java is that the tooling, both Maven and Gradle, still stucks.

show 7 replies