PlatformLogger: isLoggable performance / waste due to HashMap<Integer, Level> leads to Integer allocations (boxing)
Mandy Chung
mandy.chung at oracle.com
Wed Mar 20 17:16:26 UTC 2013
Hi Laurent,
Thank you for signing the OCA. Your contribution is very welcome. You
can submit a patch for this bug (see [1]) to Core libraries group which
owns logging. Jim Gish and I will sponsor it.
Thanks
Mandy
[1] http://openjdk.java.net/contribute/
On 3/20/2013 5:45 AM, Laurent Bourgès wrote:
> 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
> <mailto: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