RFE: Refactor java.util.Optional and add NonNull checks
Oleksii Kucheruk
iselo+openjdk at raccoons.co
Mon Aug 28 14:21:32 UTC 2023
Yes, it's 2 subclasses hierarchy.
https://github.com/iselo/jdk-optional/tree/master/lib/src
I'm open to learning about my pitfalls.
# JMH version: 1.36
# VM version: JDK 20.0.2, Java HotSpot(TM) 64-Bit Server VM, 20.0.2+9-78
# VM invoker:
/Library/Java/JavaVirtualMachines/jdk-20.jdk/Contents/Home/bin/java
# VM options: -Dfile.encoding=UTF-8
-Djava.io.tmpdir=/Users/iselo/IdeaProjects/jmh/lib/build/tmp/jmh
-Duser.country=GB -Duser.language=en -Duser.variant
# Blackhole mode: compiler (auto-detected, use
-Djmh.blackhole.autoDetect=false to disable)
# Warmup: 2 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
On Mon, Aug 28, 2023 at 4:04 PM - <liangchenblue at gmail.com> wrote:
> Can you share the benchmark code? Maybe C2 profiled class hierarchy,
> determined your optional only has 2 subclasses and can thus generate
> optimized machine code. Or maybe your benchmark is wrong that the result is
> not consumed by blackhole so the whole benchmark is moot. We need the code
> to make a verdict.
>
> Chen
>
> On Mon, Aug 28, 2023 at 6:27 PM Oleksii Kucheruk <
> iselo+openjdk at raccoons.co> wrote:
>
>> Thank you Quân.
>>
>> All you saying guys make sense.
>> Yes there is a difference calling methods by pointer and involving
>> vtable. But one thing didn't came out of my head:
>> If virtual dispatch dispatch is 10-time more expensive and polymorphism
>> is a performance killer so how combinations of "return optional -
>> isPresent- get" or "return empty - isEmpty" with virtual dispatch and
>> memory allocation for every empty or sole but polymorph empty could perform
>> equally or even beat the near zero-cost abstraction of Value classes
>> according to JMH?
>>
>> Maybe private final static nested EmptyOptional invokevirtual bytecode refers
>> a final methods and it need not have a vtable slot allocated. This means
>> that, after linking, an invokevirtual bytecode might in fact collapse into
>> the equivalent of an invokestatic bytecode.
>> The question of full dynamic with anonymous overloading is still unclear
>> to me.
>>
>> Benchmark Mode Cnt
>> Score Error Units
>>
>> *OptionalComboIsPresentGet.opDynamicAnonymous thrpt 30
>> 1059868995.937 ± 51273067.116 ops/s*
>>
>> OptionalComboIsPresentGet.opJdk thrpt 30 1017505800.567
>> ± 16007847.872 ops/s
>>
>> OptionalComboIsPresentGet.opNestedStatic thrpt 30 951493332.957
>> ± 12167812.398 ops/s
>>
>>
>> OptionalComboOfIsPresentGetTest.opDynamicAnonymous thrpt 30 1426348334.485
>> ± 20889455.433 ops/s
>>
>> *OptionalComboOfIsPresentGetTest.opJdk thrpt 30
>> 1435570789.822 ± 16587538.659 ops/s*
>>
>> OptionalComboOfIsPresentGetTest.opNestedStatic thrpt 30 1418853644.215
>> ± 27755398.645 ops/s
>>
>>
>> *OptionalComboOfNullableIsEmptyTest.opDynamicAnonymous thrpt 30
>> 1434399595.592 ± 14749443.903 ops/s*
>>
>> OptionalComboOfNullableIsEmptyTest.opJdk thrpt 30 1255375023.531
>> ± 107403119.573 ops/s
>>
>> OptionalComboOfNullableIsEmptyTest.opNestedStatic thrpt 30 1415705702.964
>> ± 26328318.672 ops/s
>>
>>
>> OptionalEmptyTest.opDynamicAnonymous thrpt 30 1417895521.763
>> ± 26311640.557 ops/s
>>
>> *OptionalEmptyTest.opJdk thrpt 30
>> 1423940087.962 ± 18515297.860 ops/s*
>>
>> OptionalEmptyTest.opNestedStatic thrpt 30 1424779162.084
>> ± 20288793.337 ops/s
>>
>>
>> OptionalGetTest.emDynamicAnonymous thrpt 30 569132.235
>> ± 11089.903 ops/s
>>
>> *OptionalGetTest.emJdk thrpt 30
>> 549764.038 ± 14768.566 ops/s*
>>
>> OptionalGetTest.emNestedStatic thrpt 30 568308.722
>> ± 6075.671 ops/s
>>
>>
>> OptionalGetTest.opDynamicAnonymous thrpt 30 953788065.414
>> ± 7043419.784 ops/s
>>
>> *OptionalGetTest.opJdk thrpt 30
>> 1015679232.049 ± 11726562.032 ops/s*
>>
>> OptionalGetTest.opNestedStatic thrpt 30 944209513.151
>> ± 17718342.519 ops/s
>>
>>
>> OptionalIsPresentTest.emDynamicAnonymous thrpt 30 869086292.481
>> ± 11668636.886 ops/s
>>
>> *OptionalIsPresentTest.emJdk thrpt 30
>> 977042244.447 ± 44558020.220 ops/s*
>>
>> OptionalIsPresentTest.emNestedStatic thrpt 30 953424664.631
>> ± 8448564.963 ops/s
>>
>>
>> OptionalIsPresentTest.opDynamicAnonymous thrpt 30 953285872.799
>> ± 6562894.941 ops/s
>>
>> *OptionalIsPresentTest.opJdk thrpt 30
>> 1006926903.885 ± 18055487.689 ops/s*
>>
>> OptionalIsPresentTest.opNestedStatic thrpt 30 954121471.431
>> ± 6829528.708 ops/s
>>
>>
>> OptionalOfNullableTest.emDynamicAnonymous thrpt 30 1422939780.266
>> ± 21693247.354 ops/s
>>
>> *OptionalOfNullableTest.emJdk thrpt 30
>> 1423340184.237 ± 21296444.017 ops/s*
>>
>> OptionalOfNullableTest.emNestedStatic thrpt 30 1414965236.385
>> ± 26021734.344 ops/s
>>
>>
>> OptionalOfNullableTest.opDynamicAnonymous thrpt 30 1412594454.538
>> ± 28123612.298 ops/s
>>
>> *OptionalOfNullableTest.opJdk thrpt 30
>> 1427924599.589 ± 23469517.835 ops/s*
>>
>> OptionalOfNullableTest.opNestedStatic thrpt 30 1420053175.637
>> ± 21170929.571 ops/s
>>
>>
>> OptionalOfTest.emDynamicAnonymous thrpt 30 549907.559
>> ± 14560.954 ops/s
>>
>> OptionalOfTest.emJdk thrpt 30 557858.903
>> ± 2851.076 ops/s
>>
>> *OptionalOfTest.emNestedStatic thrpt 30
>> 548655.854 ± 10875.674 ops/s*
>>
>>
>> OptionalOfTest.opDynamicAnonymous thrpt 30 1411307270.435
>> ± 19832207.408 ops/s
>>
>> OptionalOfTest.opJdk thrpt 30 1400651442.726
>> ± 23641065.234 ops/s
>>
>> *OptionalOfTest.opNestedStatic thrpt 30
>> 1433953744.298 ± 21414147.936 ops/s*
>>
>>
>> On Mon, Aug 28, 2023 at 11:13 AM Quân Anh Mai <anhmdq at gmail.com> wrote:
>>
>>> Hi,
>>>
>>> Polymorphism is a performance killer. Normally this does not matter
>>> much, but for thin wrappers such as Optional, this is one of the most
>>> important factor.
>>>
>>> Behaviour polymorphism requires virtual dispatch and prevents inlining.
>>> This is really detrimental, as simple operations such as a getter which is
>>> previously only consisted of a memory load, in the presence of polymorphism
>>> would have to go through a 10-time more expensive virtual dispatch.
>>> Function calls, especially virtual ones, are also opaque and prohibit
>>> compiler optimisations.
>>>
>>> Layout polymorphism prevents direct accesses and requires indirection.
>>> This means that for every instance of Optional created a memory allocation
>>> is required. Optional is expected to be a near zero-cost abstraction in the
>>> presence of Value classes, so making it polymorphic is unacceptable.
>>>
>>> Thanks.
>>>
>>> On Wed, 23 Aug 2023 at 22:43, Oleksii Kucheruk <
>>> iselo+openjdk at raccoons.co> wrote:
>>>
>>>> Hi there.
>>>> I have found that `java.util.Optional` is written procedural style and
>>>> has `ifnonnull` checks in each method. I propose to refactor `Optional` in
>>>> accordance to OOP-style. This will eliminates all unnecessary
>>>> `if`-statements, removes duplications and reduces bytecode size more then
>>>> twice.
>>>>
>>>> I have two solutions:
>>>> 1. Completely dynamic that avoids single static `EMPTY` instance and
>>>> unchecked casting of each `Optional.empty()`
>>>> 2. Preserving original single static `EMPTY` per VM.
>>>>
>>>> Also there are couple methods that throws NPE due to calling methods on
>>>> null-objects and requires to add `Objects.requireNonNull(...)`.
>>>>
>>>> OptionalInt, OptionalDouble, OptionalLong could be refactored same way
>>>> even with remove of additional boolean variable `isPresent`.
>>>>
>>>> Since I'm new here any guidance will be helpful.
>>>> Thank you in advance.
>>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20230828/220275b1/attachment-0001.htm>
More information about the core-libs-dev
mailing list