8010309 : PlatformLogger: isLoggable performance / waste due to HashMap<Integer, Level> leads to Integer allocations (boxing)

Laurent Bourgès bourges.laurent at gmail.com
Thu Mar 28 09:34:22 UTC 2013


Sorry for my previous comment: JavaLoggerProxy static initialization is not
called (false argument);

I made the test:
JavaLoggerProxy: static initialization...
LoggingSupport.isAvailable: true
java.lang.Throwable
    at
sun.util.logging.PlatformLogger$JavaLoggerProxy.<clinit>(PlatformLogger.java:562)
    at
sun.util.logging.PlatformLogger.redirectToJavaLoggerProxy(PlatformLogger.java:200)
    at
sun.util.logging.PlatformLogger.redirectPlatformLoggers(PlatformLogger.java:190)
    at java.util.logging.LogManager$2.run(LogManager.java:290)
    at java.util.logging.LogManager$2.run(LogManager.java:285)
    at java.security.AccessController.doPrivileged(Native Method)
    at
java.util.logging.LogManager.readPrimordialConfiguration(LogManager.java:285)
    at java.util.logging.LogManager.getLogManager(LogManager.java:267)
    at java.util.logging.Logger.<init>(Logger.java:280)
    at java.util.logging.LogManager$RootLogger.<init>(LogManager.java:1372)
    at java.util.logging.LogManager$RootLogger.<init>(LogManager.java:1370)
    at java.util.logging.LogManager$1.run(LogManager.java:198)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.util.logging.LogManager.<clinit>(LogManager.java:175)
    at test.PlatformLoggerBenchmark.main(PlatformLoggerBenchmark.java:39)

Maybe you should introduce an explicit variable to make it more obvious:
        boolean initialize = false; // lazy initialization

        // force loading of all JavaLoggerProxy (sub)classes to make JIT
de-optimizations
        // less probable.  Don't initialize JavaLoggerProxy class since
        // java.util.logging may not be enabled.
        try {

Class.forName("sun.util.logging.PlatformLogger$DefaultLoggerProxy",
                          initialize,
                          PlatformLogger.class.getClassLoader());
            Class.forName("sun.util.logging.PlatformLogger$JavaLoggerProxy",
                          initialize,
                          PlatformLogger.class.getClassLoader());
        } catch (ClassNotFoundException ex) {
            throw new InternalError(ex);
        }

Laurent


2013/3/28 Laurent Bourgès <bourges.laurent at gmail.com>

> Dear Mandy & Peter,
>
> I will test your patch asap (+ benchmark)
>
> Few comments below:
>
>
>> Webrev at:
>>    http://cr.openjdk.java.net/~mchung/jdk8/webrevs/8010309/webrev.00/
>>
>> Please let me know if you don't agree with these changes or you find any
>> performance issue.
>>
>> 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.
>>
>
> I think it leads to early initialization of JUL Levels (when JUL is maybe
> not available or initialized) from PlatformLogger static initialization:
>
>         // initialize javaLevel fields for mapping from Level enum ->
> j.u.l.Level object
>         static {
>             for (Level level : Level.values()) {
>                 level.javaLevel = LoggingSupport.parseLevel(level.name());
>             }
>         }
>
> I thought Level.javaLevel should only be defined ONCE JUL is available
> i.e. once PlatformLogger.redirectPlatformLoggers() is called.
> I propose to move the javaLevel initialization in this method:
>
>     public static synchronized void redirectPlatformLoggers() {
>         if (loggingEnabled || !LoggingSupport.isAvailable()) return;
>
>             // Resolve JUL Levels ONLY when JUL is initialized by
> application code:
>             for (Level level : Level.values()) {
>                 level.javaLevel = LoggingSupport.parseLevel(level.name());
>             }
>
> ...
> }
>
>
>
>>
>> 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.
>>
>
> Good idea.
>
>
>>
>> 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.
>>
>
> If getLevel() is useless, why keep it in this proprietary API ? (only for
> test purposes) ?
>
>
>>
>>
>>  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).
>>
>
> I disagree Peter: JUL has its own formatting code: patterns ... and more
> efficient than DefaultLoggerProxy.formatMessage().
>
> The good question relies in the comment: why convert object args into
> String early as JUL can do formatting / conversion?
> What does mean:
>             // only pass String objects to the j.u.l.Logger which may
>             // be created by untrusted code
> ?
> to avoid security issues ?
>
> Laurent
>
>



More information about the core-libs-dev mailing list