1 Reply Latest reply on Jun 1, 2006 11:02 AM by edisongustavo

    Compiler performance problem

    edisongustavo

      Well, I was using reflection in a class of mine and had some performance problems, since the method that is using reflection runs thousands of times in the server.
      Then I tried to use javassist, it would be much faster, but that's not what happened, the performance became really bad. Much worse than the reflection version.
      I'm using the javassist runtime compiler and the generated class takes too long to execute (the compiling time is not the problem, since it will do it only once).
      The measured time is really the execution of the generated class (not compiling).
      I think the problem is the code generated by javassist's compiler, since the same class compiled by hand had a pretty good performance. (I captured eclipse's console and pasted on a new file, then my benchmark used the new compiled version).

      The generated class is the following:

      package com.hoplon.bitverse.handlers;
      
      import com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.BaseHandler;
      
      public class MyHandlerSelecter implements HandlerCaller {
       private BaseHandler handler;
      
       public MyHandlerSelecter(BaseHandler handler) {
       this.handler = handler;
       }
      
       public boolean selectHandlers(Class idClass, Object[] fullArgs) throws RuntimeException {
       if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType1.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType1) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType7.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType7) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType6.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType6) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType5.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType5) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType4.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType4) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType3.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType3) fullArgs[0]);
       } else if (idClass
       .equals(com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType2.class)) {
       handler
       .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType2) fullArgs[0]);
       } else {
       return false;
       }
       return true;
       }
      
      }
      


      My benchmark code (a JUnit test) is the following:
      package com.hoplon.bitverse.handlers.performance;
      
      import junit.extensions.TestSetup;
      import junit.framework.Test;
      import junit.framework.TestCase;
      import junit.framework.TestSuite;
      
      import com.hoplon.bitverse.handlers.BytecodeHandler;
      
      public class BytecodeHandlerPerformanceTest extends TestCase {
       public static Test suite() {
       TestSuite suite = new TestSuite(BytecodeHandlerPerformanceTest.class);
       TestSetup testSetup = new TestSetup(suite) {
       public void setUp() {
       System.out.println("Number of calls" + NUMBER_HANDLER_CALLS);
       System.out.println("Total calls" + TOTAL_HANDLER_CALLS);
       System.out.println("Number of messages handled" + 7);
       }
      
       };
       return testSetup;
       }
      
       public interface Message {
       public int getId();
       }
      
       private static final int NUMBER_HANDLER_CALLS = 100000;
      
       private static final int NUMBER_TEST_CALLS = 1;
      
       private static final double IF_ELSE_THROUGHPUT = 2;
      
       private static final double SWITCH_CASE_THROUGHPUT = 2;
      
       private static final double TOTAL_HANDLER_CALLS = NUMBER_HANDLER_CALLS
       * NUMBER_TEST_CALLS;
      
       private BytecodeHandler bh;
      
       private BaseHandler handler;
      
       private Message[] messages;
      
       private static boolean hasCalledBH = false;
      
       private static double averageBHTime;
      
       private static int dummy = 0;
      
       public void setUp() throws Exception {
       handler = new BaseHandler();
      
       messages = new Message[7];
       messages[0] = new MessageType1();
       messages[1] = new MessageType2();
       messages[2] = new MessageType3();
       messages[3] = new MessageType4();
       messages[4] = new MessageType5();
       messages[5] = new MessageType6();
       messages[6] = new MessageType7();
      
       if (!hasCalledBH) {
       runBytecodeHandler();
       hasCalledBH = true;
       }
       }
      
       private void runBytecodeHandler() throws Exception {
       bh = new BytecodeHandler(new BaseHandler(),
       new Class[] { Message.class }, "handle");
       long shortestBHTest = Long.MAX_VALUE;
       long longestBHTest = Long.MIN_VALUE;
       double totalHandlerTime = 0;
       for (int i = 0; i < NUMBER_TEST_CALLS; i++) {
       long timeBeforeBytecodeHandlerCall = System.currentTimeMillis();
       for (int j = 0; j < NUMBER_HANDLER_CALLS; j++) {
       callBytecodeHandler();
       }
       long timeSpentBHCalls = System.currentTimeMillis()
       - timeBeforeBytecodeHandlerCall;
      
       if (timeSpentBHCalls > longestBHTest) {
       longestBHTest = timeSpentBHCalls;
       }
       if (timeSpentBHCalls < shortestBHTest) {
       shortestBHTest = timeSpentBHCalls;
       }
       totalHandlerTime += timeSpentBHCalls;
       }
       averageBHTime = totalHandlerTime / TOTAL_HANDLER_CALLS;
       System.out.println("## Bytecode handler");
       System.out.println("Total time" + totalHandlerTime + "ms");
       System.out.println("Average time" + averageBHTime + "ms");
       System.out.println("Longest time" + longestBHTest + "ms");
       System.out.println("Shortest time" + shortestBHTest + "ms");
       }
      
       public void testHandlerIfElse() throws Exception {
      
       long longestHandlerTest = Long.MIN_VALUE;
       long shortestHandlerTest = Long.MAX_VALUE;
       double totalHandlerTime = 0;
      
       for (int i = 0; i < NUMBER_TEST_CALLS; i++) {
       long timeBeforeHandlerCall = System.currentTimeMillis();
       for (int j = 0; j < NUMBER_HANDLER_CALLS; j++) {
       callHandlerIfElse();
       }
       long timeSpentHandlerCalls = System.currentTimeMillis()
       - timeBeforeHandlerCall;
      
       if (timeSpentHandlerCalls > longestHandlerTest) {
       longestHandlerTest = timeSpentHandlerCalls;
       }
       if (timeSpentHandlerCalls < shortestHandlerTest) {
       shortestHandlerTest = timeSpentHandlerCalls;
       }
       totalHandlerTime += timeSpentHandlerCalls;
       }
       double averageHandlerTime = totalHandlerTime / TOTAL_HANDLER_CALLS;
       double percentBytecodeHandlerOfNormalHandler = averageBHTime * 100.0
       / averageHandlerTime;
       double percentThroughput = averageBHTime / averageHandlerTime;
       System.out.println("## Normal handler with if/else");
       System.out.println("Total time" + totalHandlerTime + "ms");
       System.out.println("Average time" + averageHandlerTime + "ms");
       System.out.println("Longest time" + longestHandlerTest + "ms");
       System.out.println("Shortest time" + shortestHandlerTest + "ms");
       System.out
       .println("Bytecode handler's time is"
       + percentBytecodeHandlerOfNormalHandler
       + "% of handler's time");
       assertTrue("Expected " + IF_ELSE_THROUGHPUT + ", but was "
       + percentThroughput, percentThroughput <= IF_ELSE_THROUGHPUT);
       }
      
       private void callHandlerIfElse() {
       Object message = messages[(int) (Math.random() * messages.length)];
       Class idClass = message.getClass();
       Object[] fullArgs = { message };
      
       if (idClass.equals(MessageType1.class)) {
       handler.handle((MessageType1) fullArgs[0]);
       } else if (idClass.equals(MessageType2.class)) {
       handler.handle((MessageType2) fullArgs[0]);
       } else if (idClass.equals(MessageType3.class)) {
       handler.handle((MessageType3) fullArgs[0]);
       } else if (idClass.equals(MessageType4.class)) {
       handler.handle((MessageType4) fullArgs[0]);
       } else if (idClass.equals(MessageType5.class)) {
       handler.handle((MessageType5) fullArgs[0]);
       } else if (idClass.equals(MessageType6.class)) {
       handler.handle((MessageType6) fullArgs[0]);
       } else if (idClass.equals(MessageType7.class)) {
       handler.handle((MessageType7) fullArgs[0]);
       } else {
       fail("If/else case not working");
       }
       }
      
       private void callBytecodeHandler() throws Exception {
       Object message = messages[(int) (Math.random() * messages.length)];
       bh.finalHandle(message.getClass(), new Object[] { message });
       }
      
       public class BaseHandler {
      
       public final void handle(MessageType1 msg) {
       dummy++;
       }
      
       public final void handle(MessageType2 msg) {
       dummy++;
       }
      
       public final void handle(MessageType3 msg) {
       dummy++;
       }
      
       public final void handle(MessageType4 msg) {
       dummy++;
       }
      
       public final void handle(MessageType5 msg) {
       dummy++;
       }
      
       public final void handle(MessageType6 msg) {
       dummy++;
       }
      
       public final void handle(MessageType7 msg) {
       dummy++;
       }
       }
      
       public static class MessageType1 implements Message {
       private static final int ID = 1;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType2 implements Message {
       private static final int ID = 2;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType3 implements Message {
       private static final int ID = 3;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType4 implements Message {
       private static final int ID = 4;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType5 implements Message {
       private static final int ID = 5;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType6 implements Message {
       private static final int ID = 6;
      
       public int getId() {
       return ID;
       }
       }
      
       public static class MessageType7 implements Message {
       private static final int ID = 7;
      
       public int getId() {
       return ID;
       }
       }
      
      }
      


      The class generated by javassist and by hand are equal, the code generating the class is the following (note that this is not the important part, but I'm putting this here just for more information:
       private static Class createHandlerCaller(HandlerCacheKey handlerData) {
      String className = "HandlerClassFor"
       + handlerData.handlerClass.getName();
       ClassPool pool = ClassPool.getDefault();
       CtClass cc = pool.makeClass(className);
      
       /* Make the class implement the interface */
       try {
       cc.addInterface(pool.get(HandlerCaller.class.getName()));
       } catch (NotFoundException e) {
       e.printStackTrace();
       }
      
       /* Add the field representing the handler class */
       try {
       cc.addField(new CtField(pool
       .get(handlerData.handlerClass.getName()), "handler", cc));
       } catch (CannotCompileException e2) {
       e2.printStackTrace();
       } catch (NotFoundException e2) {
       e2.printStackTrace();
       }
      
       /* Add a constructor to initialize the handler class */
       try {
       CtClass[] parameters = { pool.get(handlerData.handlerClass
       .getName()) };
       cc.addConstructor(CtNewConstructor.make(parameters, null,
       "handler = $1;", cc));
       } catch (CannotCompileException e2) {
       e2.printStackTrace();
       } catch (NotFoundException e) {
       e.printStackTrace();
       }
      
       StringBuffer methodBody = getMethodBody(handlerData);
      
       /* Add the implemented method of the interface */
       try {
       CtClass[] parameters = { pool.get(Class.class.getName()),
       pool.get(Object.class.getName() + "[]") };
       CtClass[] exceptions = { pool.get(RuntimeException.class.getName()) };
       cc.addMethod(CtNewMethod.make(CtPrimitiveType.booleanType,
       "selectHandlers", parameters, exceptions, "{ "
       + methodBody.toString() + " }", cc));
       } catch (CannotCompileException e1) {
       e1.printStackTrace();
       } catch (NotFoundException e) {
       e.printStackTrace();
       }
      
       Class result = null;
       try {
       result = cc.toClass();
       } catch (CannotCompileException e) {
       e.printStackTrace();
       } catch (IllegalArgumentException e) {
       e.printStackTrace();
       } catch (SecurityException e) {
       e.printStackTrace();
       } catch (Exception e) {
       e.printStackTrace();
       }
      return result;
      }
      
      private static class HandlerCacheKey {
       public final Class handlerClass;
      
       public final Class[] handledClasses;
      
       public final String handlerMethodName;
      
       private HandlerCacheKey(Class handlerClass, Class[] handledClasses,
       String handledMethodName) {
       if (handlerClass == null || handledClasses == null
       || handledMethodName == null) {
       throw new NullPointerException();
       }
       this.handlerClass = handlerClass;
       this.handledClasses = handledClasses;
       this.handlerMethodName = handledMethodName;
       }
      }
      
      
      
      private static StringBuffer getMethodBody(HandlerCacheKey handlerData)
       throws SecurityException, IllegalArgumentException {
       Method[] methods = handlerData.handlerClass.getMethods();
      
       StringBuffer methodBody = new StringBuffer();
       Set mappedClasses = new HashSet();
      
       boolean isFirst = true;
       for (int methodsIndex = methods.length - 1; methodsIndex >= 0; methodsIndex--) {
       if (methods[methodsIndex].getName().equals(
       handlerData.handlerMethodName)) {
       Class[] params = methods[methodsIndex].getParameterTypes();
       int idArgPosition = 0;
       int idArgsFound = 0;
       for (int argIndex = params.length - 1; argIndex >= 0; argIndex--) {
       for (int idClassesIndex = handlerData.handledClasses.length - 1; idClassesIndex >= 0; idClassesIndex--) {
       if (handlerData.handledClasses[idClassesIndex]
       .isAssignableFrom(params[argIndex])) {
       idArgPosition = argIndex;
       idArgsFound++;
       }
       }
       }
       if (idArgsFound == 1) {
       // All right, found one Identifier argument
       Class idClass = params[idArgPosition];
      
       if (!mappedClasses.contains(idClass)) {
       StringBuffer handleParams = new StringBuffer();
       for (int i = 0; i < params.length; i++) {
       if (params.isPrimitive()) {
       Class cls = getCorrespondingClassToPrimitiveType(params);
       String primitiveCommand = getCommandToPrimitiveType(cls);
       handleParams.append("((").append(cls.getName())
       .append(")$2[").append(i).append("]).")
       .append(primitiveCommand).append(",");
       } else {
       handleParams.append("(").append(
       params.getName()).append(")$2[")
       .append(i).append("],");
       }
       }
       handleParams.deleteCharAt(handleParams.length() - 1);
      
       if (!isFirst) {
       methodBody.append("else ");
       }
       isFirst = false;
       methodBody.append("if ($1.equals(" + idClass.getName()
       + ".class)) {");
       methodBody.append("handler.handle(" + handleParams
       + ");");
       methodBody.append("}");
      
       mappedClasses.add(idClass);
       } else {
       // Wrong, this class was already used as an identifier
       throw new IllegalArgumentException("Identifier class "
       + idClass.getName()
       + " is used more than once in handler "
       + handlerData.handlerClass.getName());
       }
       } else {
       // Wrong, found none or more then one identifier class
       throw new IllegalArgumentException(
       "Handler method "
       + methods[methodsIndex]
       + " contains "
       + idArgsFound
       + " identifier parameters; must have one and only one");
       }
       }
       }
       methodBody.append("else { return false; } return true;");
       return methodBody;
       }
      


        • 1. Re: Compiler performance problem
          edisongustavo

          lol, after looking at the bytecode generated and running a decompiler at the code compiled by javassist I found out that javassist was calling a Class.forName() instead of using "className.class", and this was the problem.
          To avoid this I created Class fields of my generated class representing the class I wanted to compare, so it would run the Class.forName() only once.

          I don't know if this is a bug, but now the performance is wonderful! Better than eclipse version :)

          The new version:

          public class MyHandlerSelecter implements HandlerCaller {
           private static final Class class1 = com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType1.class;
          ...
          ...
          
          public boolean selectHandlers(Class idClass, Object[] fullArgs) {
           if (idClass.equals(class1)) {
           handler
           .handle((com.hoplon.bitverse.handlers.performance.BytecodeHandlerPerformanceTest.MessageType1) fullArgs[0]);
          ...
          ...
          }