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

Mandy Chung mandy.chung at oracle.com
Tue Mar 19 17:41:08 UTC 2013


Hi Laurent,

Thanks for the contribution.  I agree that the map can be replaced with 
a direct mapping from a int value to Level object and avoid the 
autoboxing conversion.

I have filed a bug to track this and target this for JDK8:
   http://bugs.sun.com/bugdatabase/view_bug.do?bug_id= 8010309

Thanks
Mandy

On 3/19/13 5:19 AM, Laurent Bourgès wrote:
> Dear all,
>
> I run recently netbeans profiler on my swing application (Aspro2:
> http://www.jmmc.fr/aspro) under linux x64 platform and figured out a
> performance and waste issue related to PlatformLogger.
>
> Actually, the JavaLogger implementation uses a Map<Integer, Object>
> levelObjects to store mapping between PlatformLogger's levels (int) and JUL
> Level instances.
>
> However, the isLoggable(int level) method is highly used by awt project and
> other JDK projects and it leads to many Integer allocations as autoboxing
> converts the level as int to an Integer instance used by the Map.get() call.
>
>      /**
>       * JavaLogger forwards all the calls to its corresponding
>       * java.util.logging.Logger object.
>       */
>      static class JavaLogger extends LoggerProxy {
>          private static final* Map<Integer, Object>* levelObjects = new
> HashMap<>();
> ...
>          public boolean isLoggable(*int level*) {
>              return LoggingSupport.isLoggable(javaLogger, *
> levelObjects.get(level)*);
>          }
>      }
>
> I wrote a simple test to illustrate that performance / waste problem:
> PlatformLoggerTest that simply performs 1 million DISABLED log statements:
>              if (log.isLoggable(PlatformLogger.FINEST)) {
>                  log.finest("test PlatformLogger.FINEST");
>              }
>
> As you can see in screenshots:
> - 5 million Integer instances are allocated
> - Integer.valueOf(int) is called 5 million times (autoboxing)
> - HashMap.get() represents 30% of the test time
> - patched PlatformLogger is 3x times faster
> [jvm options: -Xms8m -Xmx8m -verbose:gc]
>
> I suggest you to use an alternative way to map PlatformLogger's levels
> (int) and JUL Level instances to fix that performance / memory issue: I
> added the getLevel(int level) method that performs a switch case to return
> the corresponding Level object (quick and dirty solution).
>
> I advocate this is not a very clean solution but I prefer efficiency here:
> any better solution may be appropriate to avoid at least Integer allocation
> and maybe enhance performance.
>
> Best regards,
> Laurent Bourgès
>
> PS: here is the patch as text:
>
> # This patch file was generated by NetBeans IDE
> # It uses platform neutral UTF-8 encoding and \n newlines.
> --- PlatformLogger.java (6767)
> +++ PlatformLogger.java (6768)
> @@ -468,31 +468,13 @@
>        * java.util.logging.Logger object.
>        */
>       static class JavaLogger extends LoggerProxy {
> -        /** Note: using Integer keys leads to a lot of new Integer
> instances !! */
> -        private static final Map<Integer, Object>  levelObjects = new
> HashMap<>();
> -        /** fastest mapping to Level instances from PlatformLogger level
> as integer */
> -        private static final Object[] fastLevelObjects;
> -
> -
> +        private static final Map<Integer, Object>  levelObjects =
> +            new HashMap<>();
> +
>           static {
>               if (LoggingSupport.isAvailable()) {
>                   // initialize the map to Level objects
>                   getLevelObjects();
> -
> -                // initialize the fastLevelObjects:
> -                fastLevelObjects = new Object[] {
> -                    LoggingSupport.parseLevel(getLevelName(OFF)),       //
> 0
> -                    LoggingSupport.parseLevel(getLevelName(SEVERE)),    //
> 1
> -                    LoggingSupport.parseLevel(getLevelName(WARNING)),   //
> 2
> -                    LoggingSupport.parseLevel(getLevelName(INFO)),      //
> 3
> -                    LoggingSupport.parseLevel(getLevelName(CONFIG)),    //
> 4
> -                    LoggingSupport.parseLevel(getLevelName(FINE)),      //
> 5
> -                    LoggingSupport.parseLevel(getLevelName(FINER)),     //
> 6
> -                    LoggingSupport.parseLevel(getLevelName(FINEST)),    //
> 7
> -                    LoggingSupport.parseLevel(getLevelName(ALL))        //
> 8
> -                };
> -            } else {
> -                fastLevelObjects = null; // check null
>               }
>           }
>
> @@ -515,7 +497,7 @@
>               this.javaLogger = LoggingSupport.getLogger(name);
>               if (level != 0) {
>                   // level has been updated and so set the Logger's level
> -                LoggingSupport.setLevel(javaLogger, getLevel(level));
> +                LoggingSupport.setLevel(javaLogger,
> levelObjects.get(level));
>               }
>           }
>
> @@ -526,11 +508,11 @@
>           * not be updated.
>           */
>           void doLog(int level, String msg) {
> -            LoggingSupport.log(javaLogger, getLevel(level), msg);
> +            LoggingSupport.log(javaLogger, levelObjects.get(level), msg);
>           }
>
>           void doLog(int level, String msg, Throwable t) {
> -            LoggingSupport.log(javaLogger, getLevel(level), msg, t);
> +            LoggingSupport.log(javaLogger, levelObjects.get(level), msg,
> t);
>           }
>
>           void doLog(int level, String msg, Object... params) {
> @@ -544,12 +526,12 @@
>               for (int i = 0; i<  len; i++) {
>                   sparams [i] = String.valueOf(params[i]);
>               }
> -            LoggingSupport.log(javaLogger, getLevel(level), msg, sparams);
> +            LoggingSupport.log(javaLogger, levelObjects.get(level), msg,
> sparams);
>           }
>
>           boolean isEnabled() {
>               Object level = LoggingSupport.getLevel(javaLogger);
> -            return level == null || level.equals(getLevel(OFF)) == false;
> +            return level == null || level.equals(levelObjects.get(OFF)) ==
> false;
>           }
>
>           int getLevel() {
> @@ -566,34 +548,12 @@
>
>           void setLevel(int newLevel) {
>               levelValue = newLevel;
> -            LoggingSupport.setLevel(javaLogger, getLevel(newLevel));
> +            LoggingSupport.setLevel(javaLogger,
> levelObjects.get(newLevel));
>           }
>
>           public boolean isLoggable(int level) {
> -            return LoggingSupport.isLoggable(javaLogger, getLevel(level));
> +            return LoggingSupport.isLoggable(javaLogger,
> levelObjects.get(level));
>           }
> -
> -        /**
> -         * Return the corresponding level object (fastest implementation)
> -         * @param level PlatformLogger level as primitive integer
> -         * @return Object (JUL Level instance)
> -         */
> -        private static Object getLevel(final int level) {
> -            // higher occurences first (finest, fine, finer, info):
> -            switch (level) {
> -                case FINEST  : return fastLevelObjects[7];
> -                case FINE    : return fastLevelObjects[5];
> -                case FINER   : return fastLevelObjects[6];
> -                case INFO    : return fastLevelObjects[3];
> -                case CONFIG  : return fastLevelObjects[4];
> -                case WARNING : return fastLevelObjects[2];
> -                case SEVERE  : return fastLevelObjects[1];
> -                case ALL     : return fastLevelObjects[8];
> -                case OFF     : return fastLevelObjects[0];
> -                default      : return null;
> -            }
> -        }
> -
>       }
>
>       private static String getLevelName(int level) {




More information about the core-libs-dev mailing list