1. 32

  2. 4

    Using Optional values in an extremely performance sensitive Java code is likely a bad idea. All JVMs tested here failed to optimize them out.

    Are these actually optimizations Java could be doing but hasn’t gotten to yet? Or are there historic or language reasons preventing these optimizations indefinitely?

    1. 4

      Java made one specific mistake that has caused performance problems in every implementation since: new T() is guaranteed to return an object that compares not-identical to any other object. This means that constructing the Optional must create a new object, which must compare equal to any other reference to the same object but not equal to any other instance. An Optional in Java should be two words (class pointer, nullable pointer to the thing), which means that allocating a lot of them fills the GC with tiny short-lived objects.

      In contrast, in Rust, C++, or C#, you can implement Optional in the language as a value type. This is copied on the stack (which, since it fits in 1-2 registers [it doesn’t need the class pointer]) is about as cheap as copying the pointer to the object in Java.

      In theory a JVM could detect that the Optional is never captured and just replace it with a nullable pointer in the implementation but that’s quite difficult. It’s not worth doing the work until Optional is widespread and it’s not a good idea to use Optional enough that it would cause perf problems in your Java program until JVMs do this optimisation.

      1. 3

        I’m not the author so I’m not sure - but due to this article I learned about OptionalLong and it’s sibilngs for int/double primitive types. I wonder if the benchmarks would have been any different without the (I assume) autoboxing that is happening with Optional.of(long)

      2. 1

        Java is much older than Rust and carries backward compatibility weights. I am surprised it is not much slower.

        1. 2

          C++ is much older than java and I’d be surprised if it wasn’t in the same performance ballpark than Rust for optional. Old age is not an excuse for bad design.

          1. 5

            Are you sure it is bad design? Perhaps Java’s design just makes different trade-offs for performance (like consistency or lower mental model overhead or… I’m sure there could be many).

            1. 1

              I can confidently say that it isn’t for consistency or mental overhead when compared to c++‘s generic implementation. Else this mess wouldn’t exist: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/package-summary.html and requiring fall back on external libs if you want more arguments, like https://projectreactor.io/docs/extra/3.2.0.M3/api/reactor/function/Consumer8.html

              1. 1

                I can confidently disagree, having seen the C++ standard library implementations of gcc, llvm, msvc, and a few others.

                1. 1

                  why would the stdlib implementation matters ? what matters is that as a C++ dev I can just do optional<int>, optional<float>, optional<std::string>, optional<whatever_type> while in Java I have to remember that I must use OptionalInt, OptionalLong, OptionalDouble, and Optional<T> for everything else.

                  Anecdotally, I find libc++‘s implementation (https://github.com/llvm/llvm-project/blob/main/libcxx/include/optional) fairly readable, outside of the necessary __uglification of headers (and when one takes into account all the various optimizations that it does that Java’s does not seem to do - if I read https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/Optional.java correctly, it means that Java’s Optional does not work with value types since it relies on value being nullable ? how is that remotely serious)

                  1. 1

                    What matters is that in rust I can do Option<NonZeroU32> and have a type with sizeof 4. C++ can’t do that - how is that remotely serious.

                    I hope this will help you see your replies in this thread in a different way :)

                    1. 1

                      ? Of course it can, boost::optional did this for e.g. storing optional<T&> in a pointer for years. Sure, it’s a bit of work to specialize std::optional for your NonZero type but there’s nothing technically impossible

                      1. 1

                        Not really. Sure, you could maybe handcode specializations for a few selected types? Maybe? It’s not a trivial template. But even then this only handles some explicit/short list of types. In rust this is automatic for any type with ‘spare’ bits and works recursively (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=004c4172a53ba7ab00fc7afb5e3adceb).

                        Also, since two posts before you complained about java needing external libs then don’t use boost as an excuse for c++ standard libs ;)

                    2. 1

                      If you were implementing your own generic libraries instead of just consuming them, then your view might be different.

                      For the language designers, both viewpoints are important.

                      1. 1

                        Having had to do this in Java, C# and admittedly mostly) C++, I really prefer the C++ way, especially nowadays with concepts.

                        1. 1

                          Concepts help, but they are quite recent. Also, C++ has objectively quite a few more footguns than the other languages, despite individual preferences.

              2. 3

                Java has a huge boat-anchor, the JVM. It’s stuck with a bytecode instruction set, binary format, and runtime model designed in the mid-1990s before the language had even seen serious use. There have been minor changes, like clarifying the memory concurrency model, but AFAIK no serious breaking changes.

                This makes some improvements, like stack-based objects, either impossible, or achievable only with heroic effort by the JIT and only in limited circumstances. (See also: the performance limitations of Java generics due to the requirement of type-erasure.)

            2. 1

              Is the benchmark code (+harness) available somewhere? I’d be really interested on how sum_boxed fares in Rust if you change it from Box<Option<u64>> to Option<Box<u64>> (Option<Box<u64>> is slightly more memory efficient due to niche value optimisations)