Remove redundant calls of toString()
Claes Redestad
claes.redestad at oracle.com
Wed Apr 30 12:17:46 UTC 2014
On 2014-04-30 13:08, Remi Forax wrote:
> On 04/29/2014 11:20 AM, Claes Redestad wrote:
>>
>> On 2014-04-29 09:31, Remi Forax wrote:
>>> On 04/28/2014 05:43 PM, Claes Redestad wrote:
>>>> On 04/28/2014 08:57 AM, David Holmes wrote:
>>>>> On 28/04/2014 1:05 PM, Otávio Gonçalves de Santana wrote:
>>>>>> In my opinion not, because Objects.requireNonNull is more
>>>>>> readable than
>>>>>> just string.toString. This way is more understandable which field is
>>>>>> required and doesn't impact on performance.
>>>>>
>>>>> An invocation of requireNonNull is potentially more expensive than
>>>>> the implicit null check that happens with foo.toString().
>>>>>
>>>>> David
>>>>> -----
>>>>
>>>> My thought was that these two would be inlined to the exact same
>>>> thing, so I did a quick test to see what happens when you do
>>>> foo.toString() versus Objects.requireNonNull(foo) on a set of
>>>> randomly generated String[]'s with different amounts of null
>>>> elements(0p: no null entries, 1p: 1% null entries etc):
>>>>
>>>> Benchmark Mode Samples Mean
>>>> Mean error Units
>>>> s.m.ThrowAwayBenchmark.nullToString0p thrpt 6 356653.044
>>>> 3573.707 ops/ms
>>>> s.m.ThrowAwayBenchmark.nullToString1p thrpt 6
>>>> 353128.903 2764.102 ops/ms
>>>> s.m.ThrowAwayBenchmark.nullToString10p thrpt 6
>>>> 297956.571 9580.251 ops/ms
>>>> s.m.ThrowAwayBenchmark.nullToString50p thrpt 6
>>>> 158172.036 1893.096 ops/ms
>>>> s.m.ThrowAwayBenchmark.nullToString100p thrpt 6
>>>> 18194.614 472.091 ops/ms
>>>> s.m.ThrowAwayBenchmark.requireNonNull0p thrpt 6
>>>> 357855.126 2979.090 ops/ms
>>>> s.m.ThrowAwayBenchmark.requireNonNull1p thrpt 6
>>>> 67601.134 7004.689 ops/ms
>>>> s.m.ThrowAwayBenchmark.requireNonNull10p thrpt 6
>>>> 8150.595 538.970 ops/ms
>>>> s.m.ThrowAwayBenchmark.requireNonNull50p thrpt 6
>>>> 1604.919 220.903 ops/ms
>>>> s.m.ThrowAwayBenchmark.requireNonNull100p thrpt 6
>>>> 820.626 60.752 ops/ms
>>>>
>>>> Yikes! As long as the value is never null they're inlined nicely
>>>> and neither have the upper hand performance-wise, but as soon as
>>>> you get some null values, Objects.requireNonNull degenerates much
>>>> faster than its foo.toString counterpart. I think this is a JIT
>>>> issue - optimizing exception-paths might not be the highest
>>>> priority, but Objects.requireNonNull is used pretty extensively in
>>>> the JDK and my expectation would be that it shouldn't degrade
>>>> performance when things actually are null now and then.
>>>>
>>>> /Claes
>>>
>>> This is a know issue, I think it's not related to the way the JIT
>>> handle exception path but what is called 'profile pollution'.
>>> Hotspot JITs have two ways to do a null check, either do nothing
>>> (yes nothing) and let the system do a fault and come back from dead
>>> using a signal handler, this solution is named implicit null check
>>> or by doing an explicit null check, i.e a conditional jump.
>>> Implicit null check is faster but if the receiver is null, it cost
>>> you an harm, so the VM profiles receiver to remember if the receiver
>>> of each call can be null or not. The problem is that when you call
>>> foo.toString(), the profile information is associated with the
>>> instruction that does foo.toString() while if the call is
>>> Objects.requireNonNull(foo), the profile associated with the
>>> nullcheck is stored inside the method requireNonNull, so if one call
>>> to requireNonNull in the entire program throw a NPE, the profile
>>> inside requireNonNull now register that it may fail, so for the VM
>>> all calls to requireNonNull may fail. So currently you should not
>>> use requireNonNull is the value is not required to be null*
>> I guess I should have given more details. :-) I ran my micros on a
>> number of forked VMs to ensure I don't get excessive run-to-run
>> variations, which among other things avoids profile pollution. Also I
>> sloppily collected very few samples and only did minimal warmup after
>> I saw that the micros produced scores that were orders of magnitude
>> apart and the values stabilized after less than two seconds: Flags:
>> -f 3 -wi 4 -i 2
>>
>> When I run the micros sequentially in the same VM I see some added
>> degradation for the exceptional cases, but the 0p cases still inline
>> to the optimal(?) 360k ops/ms, so while it seems profile pollution
>> might play in when provoked, HotSpot seems to manage these simple
>> micros well enough.
>
> Ok, so you have to take a look to the generated assembly code :)
> https://wiki.openjdk.java.net/display/HotSpot/PrintAssembly
I've discussed the problem with some members of the compiler team who
seem to think they have a pretty good idea about what's happening and
thought I should file an RFE for this:
https://bugs.openjdk.java.net/browse/JDK-8042127 - I suggest we continue
the discussion there and/or start a new thread on this topic.
>
>>> So the problem is that Hotspot blindly trusts the recorded profile
>>> compared to the profile that can come from the arguments of a call.
>>> The good news is that recently some patches were included in the
>>> jdk9 tree to fix or at least mitigate that issue.
>>>
>> I've now run tests on JDK8 FCS and a recent internal build of JDK9
>> and see the same characteristics. How recently are we talking here? :-)
>
> I don't know, I have seen the patches but not dive into the source
> yet, it's on my todo list, maybe it's not even activated by default.
>
>>
>>> cheers,
>>> Rémi
>>> * read that last sentence again, it seems very logical, no ?
>>>
>> While your reading of the method name and suggested approach makes
>> sense right now, the javadoc for Objects.requireNonNull states "this
>> method is designed primarily for doing parameter validation in
>> methods and constructors". To me, "parameter validation" suggests
>> that we're dealing with unknowns and that there's at least some
>> calculated risk that the input might be null, so the intent is really
>> to encourage explicit fail-fast designs, no?
>
> The great Java Book is clear on that, you shall never try to recover
> from an exception that inherits from RuntimeException :)
> A runtime exception is an exception that developers throw at the head
> of their fellow developers to indicate that they should read the
> fucking manual.
I completely agree with you, however, I still think this is something
that might happen in production systems because of reasons, so while we
can't eliminate the penalty I still think it's a reasonable request to
try and achieve a similarly graceful performance degradation as when
doing implicit null checks.
/Claes
>
>> I thus think it makes sense to try and ensure these methods have the
>> same performance characteristics as when provoking an implicit null
>> check by dereferencing an object.
>
> as I said, I think it's time to take a look to the generated assembly
> code.
>
>>
>> /Claes
>
> regards,
> Rémi
>
More information about the core-libs-dev
mailing list