by krisgenre on 3/13/23, 8:10 AM with 158 comments
by kasperni on 3/13/23, 10:02 AM
Without Valhalla
- OptionBenchmark.sumSimple avgt 5 328,110 us/op
- OptionBenchmark.sumNulls avgt 5 570,800 us/op
- OptionBenchmark.sumOptional avgt 5 2223,887 us/op
- OptionBenchmark.sumOptionalLong avgt 5 1201,987 us/op
With Valhalla
- OptionBenchmark.sumSimpleValhalla avgt 5 327,927 us/op
- OptionBenchmark.sumNullsValhalla avgt 5 584,967 us/op
- OptionBenchmark.sumOptionalValhalla avgt 5 572,833 us/op
- OptionBenchmark.sumOptionalLongValhalla avgt 5 326,949 us/op
OptionalLong is now as fast as simple sum. And SumOptional is now as fast as SumNulls. So the overhead of using OptionalLong and Optional<Long> seems to have gone away with Valhalla.
It would be great if boxing could be eliminated as well. But few people writes code like what is being benchmarked (in hot loops) in practice.
by marginalia_nu on 3/13/23, 8:57 AM
(The reason it works is because Java doesn't actually allocate new Longs for small numbers, it fetches them from a table of constants; it's always the same 256 objects that are being dereferenced. I don't know their memory layout, but I'd half expect them to be sequential in memory as that's would be much a low hanging fruit optimization. Optional<Long>'s performance is what you'd expect without these optimizations. Also in this scenario you really should use OptionalLong instead of Optional<Long> but that's beside the point ;-)
by Someone on 3/13/23, 9:39 AM
Either[null, T]
but a Either[null, boxed T, boxed null]
‘Boxed null’ is what the documentation (https://docs.oracle.com/javase/8/docs/api/java/util/Optional...) calls “an empty Optional”That means that, for example, an Optional[Byte] can have 258 different values and cannot, in general, be compiled to a ”pointer to byte” because that has only 257 different values.
Edit: reading https://news.ycombinator.com/item?id=35133241, the plan is to change that. I fear that, by the time they get around to that, lots of code will handle the cases null and Optional containing null differently, making that a breaking change.
by jeroenhd on 3/13/23, 10:09 AM
Why would Rust be cheating here? Java cannot make these types of optimizations yet (though they are likely coming with Project Valhalla) but that doesn't mean Rust should be similarly handicapped in benchmarks.
Java has many smart optimizations and advantages over Rust (being garbage collected for one, making it much easier to write code in, and runtime reflection, a blessing and a curse) and with tricks like rearranging objects to make more effective use of CPU caches you can end up writing Java that's very close in performance to native, precompiled code.
However, when it comes to raw performance, you shouldn't expect the standard JVM to come close to Rust. There is inherent overhead in the way the language and the runtime are designed. There is no "cheating" here, the algorithms are the same and some languages just produce more efficient code in these scenarios. You wouldn't slow down the JVM to make the benchmark fair for a Python implementation either!
A more interesting comparison may be compiling Java to native assembly (through Graal for example) so Java too can take advantage of not having to deal with reflection and using SIMD instructions.
Alternatively, a Java vs C# rundown would also be more interesting, as both languages serve similar purposes and solve similar problems. C#'s language-based approach to optional values has the potential to be a lot faster than Java's OOM-based approach but by how much remains to be seen.
Java vs Kotlin may also be interesting to benchmark to see if the Kotlin compiler can produce faster code than Java's Optional; both run inside the same JVM so the comparison may be even better.
by ithkuil on 3/13/23, 9:32 AM
e.g. Option<NonZeroU64> is effectively encoded and operated on as u64, but it gives the type system a way to make sure you correctly handle the case where "0" means something special for you
by cryptos on 3/13/23, 9:14 AM
by rocqua on 3/13/23, 10:11 AM
The function
fn get_optional_non_zero(n: u64) -> Option<NonZeroU64>
let i = n & 0xFF;
if i == MAGIC { None } else { NonZeroU64::new(i) }
}
Actually returns None for n = 0 or n is any multiple of 256.The resulting usage in the sum still yields the same result, because skipping zeros in an addition doesn't matter, but it is a subtle difference between this get-function compared to all of the others. It also doubles the number of None cases the code needs to handle.
by TwentyPosts on 3/13/23, 9:43 AM
Either way, glad to see that Rust is doing a good job eliminating the overhead. I'm not sure if arithmetic is the right kind of benchmark here, but it'd probably be difficult to measure the performance overhead across "real" codebases, so focusing on a tight loop microbenchmark is probably fine.
by MrBuddyCasino on 3/13/23, 9:03 AM
Hoping Java will get this one day, but probably not...
by diffuse_l on 3/13/23, 9:08 AM
Wasn't Java supposed to get support for Value types some time ago?
by Yujf on 3/13/23, 8:50 AM
by layer8 on 3/13/23, 3:16 PM
“Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.
I think routinely using it as a return value for getters would definitely be over-use.”
by dgb23 on 3/13/23, 9:54 AM
In some languages like TS, PHP or Kotlin have proper unions that you just handle with branching.
Rust lets you pattern match againt a construct that holds a value or doesn’t. Option is an actual thing there that you need to unpack in order to look inside.
In Clojure nils are everywhere. They tell you that “you’re done” in an eager recursion, or that a map doesn’t have something etc. Many functions return something or nil, and depending on what you’re doing you care about the value vs the logical implication.
nils flow naturally through your program and it’s not something you are worried about, as many functions do nil punning. Well as long as you don’t directly deal with Java - then you have to be more careful.
by chrismorgan on 3/13/23, 9:48 AM
Discussion at publication time: https://news.ycombinator.com/item?id=28887908
by gjadi on 3/13/23, 9:36 AM
This article resonate in me with the recent articles of Casey Muratori about non-pessimistic code:
Within the realm of the module, don't use pessimistic code (avoid Boxing) _but_ that doesn't prevent you to provide a safe API. E.g. the result of the loop could be wrapped if that made sense.