8010309 : PlatformLogger: isLoggable performance / waste due to HashMap<Integer, Level> leads to Integer allocations (boxing)
Mandy Chung
mandy.chung at oracle.com
Thu Mar 28 14:49:17 UTC 2013
Hi Peter, Laurent,
Thanks for the performance run. I'll push the changeset once I check
the test result.
On 3/28/2013 2:52 AM, Peter Levart wrote:
>
> ...just a nit! In the test:
>
> private static void checkPlatformLoggerLevelMapping(Level level) {
> // map the given level to PlatformLogger.Level of the same
> name and value
> PlatformLogger.Level platformLevel =
> PlatformLogger.Level.valueOf(level.getName());
> if (platformLevel == null || platformLevel.intValue() !=
> level.intValue()) {
> throw new RuntimeException("Mismatched level: " + level
> + " PlatformLogger.Level" + platformLevel);
> }
>
> The Level.valueOf(String) is guaranteed to not return null.
That's right. Will fix it.
Laurent Bourges wrote:
> Maybe you should introduce an explicit variable to make it more obvious:
> boolean initialize = false; // lazy initialization
I'll check the comments and see how I can make it clearer. I think it
is. I don't think the variable helps (perhaps add a comment next to
'false').
>
> Do you plan to apply the fix on JDK7 updates ?
I don't have any issue backporting this to jdk7u. I'll get back to you
on this one how to coordinate on this once this is baked in jdk8 repo first.
Thanks
Mandy
>
>
>
>
> Regards, Peter
>
>>
>> I have retained the part in webrev.08 that only defines loggerProxy
>> and javaLoggerProxy. I don't think the new defaultLoggerProxy
>> variable is needed. I prefer to use Class.forName to force load
>> JavaLoggerProxy class to make very explicit that we don't want the
>> class be initialized. JavaLoggerProxy.<clinit> calls
>> LoggingProxy.parseLevel unconditionally that will throw an
>> AssertionError if java.util.logging is not available. If
>> JavaLoggerProxy class is initialized, j.u.logging should be present
>> and I want to catch error if otherwise. Also Class.forName will load
>> its superclass LoggerProxy.
>>
>> As for the PlatformLogger.Level enums, having the members to be
>> defined in ascending order is fragile. It'd be more reliable to
>> specify the value in the constructor so that it can be used as the
>> isLoggable comparsion in the same way as in the Level implementation.
>>
>> I am running the jdk core regression tests on all platforms and
>> should have the result by tomorrow.
>>
>> On 3/27/2013 9:44 AM, Peter Levart wrote:
>>> Hi Mandy, Laurent,
>>>
>>> It turns out that the API change (change of type for level parameter
>>> int -> enum Level) is entirely source-compatible. The tip of JDK8
>>> (tl repo) builds without a problem. So no-one is currently using
>>> PlatformLogger.getLevel() method and assigning it to a variable or
>>> such...
>>>
>>
>> Great thanks. That is what I expect too.
>>
>>> Here's the webrev for this change:
>>>
>>> http://dl.dropbox.com/u/101777488/jdk8-tl/PlatformLogger/webrev.enumapi.01/index.html
>>>
>>> Besides doing the replacement of type and renaming and removing of
>>> the unneeded stuff, I also did some re-arrangements:
>>>
>>> - introduced a common abstract superclass for both types of
>>> LoggerProxys (DefaultLoggerProxy, JavaLoggerProxy), since
>>> JavaLoggerProxy does not need the fields of DefaultLoggerProxy and
>>> now both concrete subclasses can be declared final (makes JIT even
>>> more happy). Also the abstract LoggerProxy could host some common
>>> logic (see below about formatting)...
>>
>> That's a good idea.
>>
>>> - DefaultLoggerProxy's levelValue/effectiveLevel were given names
>>> just the opposite of their meaning. I aligned them with terminology
>>> used in j.u.l.Logger and renamed levelValue to plain level.
>>
>> Swapping these 2 variables makes sense.
>>
>>> - introduced private method
>>> DefaultLoggerProxy.deriveEffectiveLevel(level) that currently just
>>> returns defaultLevel (INFO) when given null.
>>
>> Ok.
>>
>>> I think with a little more effort, it could be possible to emulate
>>> the behaviour of j.u.l.Logger which inherits from 1st parent logger
>>> that has non-null level assigned. Of course with all the caching and
>>> invalidation...
>>
>> The only way to change the level to a non-default one is via the
>> logging configuration and enable logging. That's why there is no
>> need for DefaultLoggerProxy to inherit level from its parent logger.
>> Also there is no parent logger hierarchy maintained in
>> DefaultLoggerProxy.
>>
>>> - instead of static final DefaultLoggerProxy.defaultStream I created
>>> a private static method outputStream() that returns System.err. To
>>> accomodate for the situation when System.err is changed dynamically.
>>
>> Ok.
>>
>>> - fixed the JavaLoggerProxy.isEnabled() method. Original code was:
>>>
>>> 532 boolean isEnabled() {
>>> 533 Object level = LoggingSupport.getLevel(javaLogger);
>>> 534 return level == null || level.equals(levelObjects.get(OFF)) == false;
>>> 535 }
>>>
>>> If 'level' is null then it can be that 1st parent that has a non-nul
>>> level is "OFF". I think that in such situation all the children that
>>> don't override the level should be disabled too. The following does
>>> exactly that:
>>>
>>> 597 boolean isEnabled() {
>>> 598 return LoggingSupport.isLoggable(javaLogger, Level.OFF.javaLevel);
>>> 599 }
>>>
>>
>> That is right. Good catch.
>>
>>> That's all for 1st rev. Besides the effective level inheritance, the
>>> following method in JavaLoggerProxy also caught my eye:
>>>
>>> void doLog(Level level, String msg, Object... params) {
>>> if (!isLoggable(level)) {
>>> return;
>>> }
>>> // only pass String objects to the j.u.l.Logger which may
>>> // be created by untrusted code
>>> int len = (params != null) ? params.length : 0;
>>> Object[] sparams = new String[len];
>>> for (int i = 0; i < len; i++) {
>>> sparams [i] = String.valueOf(params[i]);
>>> }
>>> LoggingSupport.log(javaLogger, level.javaLevel, msg,
>>> sparams);
>>> }
>>>
>>> I think this could be improved if the
>>> DefaultLoggerProxy.formatMessage() is used instead of turning each
>>> parameter into a String. The method could be moved up to abstract
>>> LoggerProxy and used in both implementations so that common
>>> formatting is applied regardless of back-end used.
>>>
>>
>> Let's do this in a separate clean up as it's better to keep 8010309
>> focus on performance improvement (although we have mixed this bag
>> with some renaming).
>>
>>> The benchmarks still show stable performance:
>>>
>>
>> Great. Hope we will wrap up tomorrow.
>>
>> thanks for the contribution.
>> Mandy
>>
>>>
>>>
>>> Regards, Peter
>>>
>>>
>>> On 03/26/2013 10:37 PM, Mandy Chung wrote:
>>>> Hi Peter,
>>>>
>>>>> http://dl.dropbox.com/u/101777488/jdk8-tl/PlatformLogger/webrev.08/index.html
>>>> I'm glad that you observe similar performance improvement without
>>>> the need of method handles. I reviewed this version and realize
>>>> that the map from j.u.l.Level object to LevelEnum can be removed
>>>> entirely.
>>>>
>>>> sun.util.logging.PlatformLogger is an internal API that should only
>>>> be used by JDK. The static final int fields representing the level
>>>> value can be changed to be static final LevelEnum type instead. I
>>>> checked the JDK code that uses PlatformLogger and no code will be
>>>> impacted by the change of the type of these static fields. So it
>>>> removes the need to map from an integer value to LevelEnum. Mapping
>>>> from a j.u.l.Level to LevelEnum is trivial - the name of the
>>>> LevelEnum is the same as j.u.l.Level (e.g. LevelEnum.FINEST and
>>>> Level.FINEST), you can call LoggingSupport.getLevelName(javaLevel)
>>>> to find its name and LevelEnum.valueOf(levelName) returns the
>>>> LevelEnum instance. However, this would require more changes -
>>>> basically the methods taking "int level" as a parameter would be
>>>> modified to take LevelEnum and getLevel() would return LevelEnum
>>>> too. I think it's worth doing this cleanup to get rid of the
>>>> unnecessary conversion from int -> enum -> j.u.l.Level and vice
>>>> versa. I also recommend to rename LevelEnum to Level which is an
>>>> inner class of PlatformLogger. What do you think of this
>>>> alternative to get rid of the map?
>>>>
>>>> Some other comments of your patch:
>>>> - it renames the class JavaLogger to JavaLoggerProxy and the
>>>> variable from logger to loggerProxy. I'm fine with that.
>>>> - L162: JavaLoggerProxy.init() to force load of the class which
>>>> leads to separating the initialization of LevelEnum.javaLevel in a
>>>> new JavaLevel class. The JavaLevel utility methods are not needed
>>>> if we change the static final fields to LevelEnum.
>>>>
>>>> Have you tried:
>>>> Class.forName("sun.util.logging.PlatformLogger.JavaLoggerProxy",
>>>> false, PlatformLogger.getClassLoader());
>>>>
>>>> would this give you the same performance improvement? If so, you
>>>> can keep the static initialization in the JavaLoggerProxy class.
>>>>
>>>> Thanks for expanding the PlatformLoggerTest to cover additional
>>>> test cases. It's good that you compare the value of the
>>>> PlatformLogger static final fields with j.u.l.Level.intValue().
>>>> You now can use the API to compare the LevelEnum with Level rather
>>>> than reflection. Perhaps you can add the getName() and intValue()
>>>> methods in LevelEnum class (just a thought).
>>>>
>>>> Mandy
>>>>
>>>>
>>>> On 3/25/13 9:31 AM, Peter Levart wrote:
>>>>> Well, Laurent, Mandy,
>>>>>
>>>>> It turns out that the dispatch speed-up (or lack of slow-down to
>>>>> be precise) is possible without MethodHandles too. Maybe some VM
>>>>> guru could shed some light on this, but the following is currently
>>>>> the fastest variant:
>>>>>
>>>>> http://dl.dropbox.com/u/101777488/jdk8-tl/PlatformLogger/webrev.08/index.html
>>>>>
>>>>> What I did is a simple "if" deciding between two call-sites,
>>>>> making sure that each is only dispatching to single class. This
>>>>> only works, if both classes (LoggerProxy and JavaLoggerProxy) are
>>>>> loaded in advance, before 1st invocation on any of them is made
>>>>> (might be that using MethodHandles forced initialization of both
>>>>> classes beforehand and hence the speed-up). If I don't load
>>>>> JavaLoggerProxy before warming-up with LoggerProxy, then when
>>>>> j.u.logging is enabled, speed drops for a factor of almost 4 and
>>>>> never catches up even after very long time.
>>>>> This pre-loading also helps for a normal single call site dispatch
>>>>> that dispatches to two distinct classes, but the speed 1st drops
>>>>> when the class changes, and only catches-up after several billions
>>>>> of iterations (30s tight loop on i7).
>>>>> Now, because JavaLoggerProxy is initialized early, I had to move
>>>>> the initialization of j.u.logging.Level objects and mappings to a
>>>>> separate class "JavaLevel".
>>>>>
>>>>> Here are the benchmark results for this last iteration...
>>>>>
>>>>
>>>
>>
>
More information about the core-libs-dev
mailing list