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

Laurent Bourgès bourges.laurent at gmail.com
Wed Mar 20 12:45:41 UTC 2013


Hi mandy,

Do you want me to propose an improved patch to remove the former Map and
fix the getLevel() method ? or you prefer fix on your own ?

Is it better to discuss the fix on the bug database (still not visible) ?

Laurent

2013/3/19 Mandy Chung <mandy.chung at oracle.com>

>  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