Slow performance of StackWalker.getCallerClass() vs Reflection.getCallerClass()

Mandy Chung mandy.chung at oracle.com
Tue Jul 2 23:12:53 UTC 2019


Hi Ralph,

Thanks for this info.

Quick comments: LogRecord does not get the line number nor 
StackTraceElement.  There is cost to construct the string-based 
StackTraceElement or get the line number mapped from BCI.   And it is 
orthogonal to StackWalker::getCallerClass that is only interested in the 
Class object.

You may also be interesting in 
https://bugs.openjdk.java.net/browse/JDK-8189752 to take a snapshot of a 
stack trace (possibly the top N frames).

Mandy

On 7/2/19 2:49 PM, Ralph Goers wrote:
> The timing of this question is perfect. I have been doing some testing 
> over the last week to address 
> https://issues.apache.org/jira/browse/LOG4J2-2644 and found some 
> interesting things - although they are related to the walk() method, 
> not getCallerClass().
> 1. Using skip(n) to find a frame a couple frames up is pretty fast but 
> isn’t too much faster than finding the same element using new 
> Throwable().getStackTrace() was in Java 8.
> 2. The cost of walking the stack becomes much more costly as the 
> number of elements needing to be walked increases.
> 3. The most shocking to me was that the fastest way to traverse a 
> stack trace is to use a Function that immediately converts the Stream 
> to an array and then use an old style for loop to traverse it. 
> However, doing this is incredibly awkward because StackWalker only 
> supports streams so there is no good way to pass the value being 
> searched for into the Function. I had to resort to storing it in a 
> ThreadLocal. Having a toArray() method on StackWalker would be a lot 
> nicer, especially if I could limit the number of frames retrieved. I 
> should note that java.util.logging.LogRecord uses a Filter to walk the 
> stack which is faster than the stream methods I was originally using, 
> but is much slower than what I ended up with.
>
> As for the issue mentioned here, I believe I reported that 
> getCallerClass was much slower than the Reflection class in Java 9 and 
> opened a bug here. As I recall that was addressed and I believe I 
> verified that fix but it probably wouldn’t hurt for me to do it again.
>
> Ralph
>
>> On Jul 2, 2019, at 10:48 AM, Mandy Chung <mandy.chung at oracle.com 
>> <mailto:mandy.chung at oracle.com>> wrote:
>>
>> MethodHandles::lookup is optimized (@ForceInline) and so it may not
>> represent apple-to-apple comparison.StackWalker::getCallerClass
>> does have overhead compared to Reflection::getCallerClass and
>> need to get the microbenchmark in the jdk repo and rerun the numbers [1].
>>
>> I'm not getting how getCallerClass is used and related to access check.
>> Can you elaborate?
>>
>> Mandy
>> [1] https://bugs.openjdk.java.net/browse/JDK-8221623
>>
>>
>> On 7/2/19 6:07 AM, Kasper Nielsen wrote:
>>> Hi Remi,
>>>
>>> Yes, setting up a StackWalker is more or less free. It is just
>>> wrapping a set of options.
>>>
>>> public class StackWalkerPerf {
>>>
>>>     static final StackWalker sw =
>>> StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
>>>
>>>     @Benchmark
>>>     public StackWalker stackWalkerSetup() {
>>>         return StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
>>>     }
>>>
>>>     @Benchmark
>>>     public Class<?> stackWalkerCallerClass() {
>>>         return sw.getCallerClass();
>>>     }
>>>
>>>     @Benchmark
>>>     public Lookup reflectionCallerClass() {
>>>         return MethodHandles.lookup();
>>>     }
>>> }
>>>
>>> Benchmark                               Mode  Cnt     Score    Error 
>>>  Units
>>> StackWalkerPerf.stackWalkerSetup        avgt   10    11.958 ±  0.353 
>>>  ns/op
>>> StackWalkerPerf.reflectionCallerClass   avgt   10     8.511 ±  0.415 
>>>  ns/op
>>> StackWalkerPerf.stackWalkerCallerClass  avgt   10  1269.825 ± 66.471 
>>>  ns/op
>>>
>>> I'm using MethodHandles.lookup() in this test because it is cheapest
>>> way to invoke Reflection.getCallerClass() without any tricks.
>>> So real performance is likely better.
>>>
>>> /Kasper
>>>
>>> On Tue, 2 Jul 2019 at 13:53, Remi Forax <forax at univ-mlv.fr 
>>> <mailto:forax at univ-mlv.fr>> wrote:
>>>> Hi Kasper,
>>>> did you store the StackWalker instance in a static final field ?
>>>>
>>>> Rémi
>>>>
>>>> ----- Mail original -----
>>>>> De: "Kasper Nielsen" <kasperni at gmail.com <mailto:kasperni at gmail.com>>
>>>>> À: "core-libs-dev" <core-libs-dev at openjdk.java.net 
>>>>> <mailto:core-libs-dev at openjdk.java.net>>
>>>>> Envoyé: Mardi 2 Juillet 2019 11:09:11
>>>>> Objet: Slow performance of StackWalker.getCallerClass() vs 
>>>>> Reflection.getCallerClass()
>>>>> Hi,
>>>>>
>>>>> Are there any security reasons for why StackWalker.getCallerClass()
>>>>> cannot be made as performant as Reflection.getCallerClass()?
>>>>> StackWalker.getCallerClass() is at least 100 times slower then
>>>>> Reflection.getCallerClass() (~1000 ns/op vs ~10 ns/op).
>>>>>
>>>>> I'm trying to retrofit some existing APIs where I cannot take a Lookup
>>>>> object to do some access control checks.
>>>>> But the performance of StackWalker.getCallerClass() is making it 
>>>>> impossible.
>>>>>
>>>>> Best
>>>>>  Kasper
>>
>>
>



More information about the core-libs-dev mailing list