Skip to content

Janino 3.1.2 is 10x slower than 3.0.11 #131

@chaokunyang

Description

@chaokunyang

Compared to janino 3.0.11, janino 3.1.2 is ten times slower. Is this expected?

Reproduce

Test case

  // For 3.0.11: Total cost 98.523273 ms, average time is 1.970465 ms
  // For 3.1.2: Total cost 15863.328650 ms, average time is 317.266573 ms
  @Test
  public void benchmark() throws Exception {
    CompileUnit unit = new CompileUnit("demo.pkg1", "A", (""
        + "package demo.pkg1;\n"
        + "public class A {\n"
        + "  public static String hello() { return \"HELLO\"; }\n"
        + "}"
    ));
    // Since janino is not called frequently, we test only 50 times.
    int iterNums = 50;
    for (int i = 0; i < iterNums; i++) {
      JaninoUtils.compile(Thread.currentThread().getContextClassLoader(), unit);
    }
    long startTime = System.nanoTime();
    for (int i = 0; i < iterNums; i++) {
      JaninoUtils.compile(Thread.currentThread().getContextClassLoader(), unit);
    }
    long duration = System.nanoTime() - startTime;
    System.out.printf("Total cost %f ms, average time is %f ms",
        (double) duration / 1000_000,
        (double) duration / iterNums / 1000_000);
  }

3.1.2

package io.ray.fury.codegen;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.util.reflect.ByteArrayClassLoader;
import org.codehaus.commons.compiler.util.resource.MapResourceCreator;
import org.codehaus.commons.compiler.util.resource.MapResourceFinder;
import org.codehaus.commons.compiler.util.resource.Resource;
import org.codehaus.janino.ClassLoaderIClassLoader;
import org.codehaus.janino.Compiler;

/**
 * A util to compile code to bytecode and create classloader to load generated class.
 * Based on org.codehaus.janino.tests.CompilerTest#testInMemoryCompilation,
 * see (https://github.com/janino-compiler/janino/issues/52)
 */
public class JaninoUtils {

  public static ClassLoader compile(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    final Map<String, byte[]> classes = toBytecode(parentClassLoader, compileUnits);
    // Set up a class loader that finds and defined the generated classes.
    return new ByteArrayClassLoader(classes, parentClassLoader);
  }

  public static List<byte[]> compileToBytecode(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    final Map<String, byte[]> classes = toBytecode(parentClassLoader, compileUnits);
    List<byte[]> byteCodes = new ArrayList<>(compileUnits.length);
    for (CompileUnit unit : compileUnits) {
      String key = unit.pkg.replace(".", "/") + "/" + unit.mainClassName + ".class";
      byteCodes.add(classes.get(key));
    }
    return byteCodes;
  }

  private static Map<String, byte[]> toBytecode(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    MapResourceFinder sourceFinder = new MapResourceFinder();
    for (CompileUnit unit : compileUnits) {
      String stubFileName = unit.pkg.replace(".", "/") + "/" + unit.mainClassName + ".java";
      sourceFinder.addResource(stubFileName, unit.getCode());

      Path path = Paths.get(CodeGenerator.getCodeDir(), stubFileName).toAbsolutePath();
      try {
        path.getParent().toFile().mkdirs();
        if (CodeGenerator.deleteCodeOnExit()) {
          path.toFile().deleteOnExit();
        }
        Files.write(path, unit.getCode().getBytes());
      } catch (IOException e) {
        throw new RuntimeException(String.format("Write code file %s failed", path), e);
      }
    }

    // Storage for generated bytecode
    final Map<String, byte[]> classes = new HashMap<>();
    // Set up the compiler.
    ClassLoaderIClassLoader iClassLoader = new ClassLoaderIClassLoader(parentClassLoader);
    Compiler compiler = new Compiler();
    compiler.setSourceFinder(sourceFinder);
    compiler.setIClassLoader(iClassLoader);
    compiler.setClassFileCreator(new MapResourceCreator(classes));
    compiler.setClassFileFinder(new MapResourceFinder(classes));

    // set debug flag to get source file names and line numbers for debug and stacktrace.
    // this is also the default behaviour for javac.
    compiler.setDebugSource(true);
    compiler.setDebugLines(true);

    // Compile all sources
    try {
      compiler.compile(sourceFinder.resources().toArray(new Resource[0]));
    } catch (CompileException | IOException e) {
      StringBuilder msgBuilder = new StringBuilder("Compile error: \n");
      for (int i = 0; i < compileUnits.length; i++) {
        CompileUnit unit = compileUnits[i];
        if (i != 0) {
          msgBuilder.append('\n');
        }
        String qualifiedName = unit.pkg + "." + unit.mainClassName;
        msgBuilder.append(qualifiedName).append(":\n");
        msgBuilder.append(CodeFormatter.format(unit.getCode()));
      }
      throw new CodegenException(msgBuilder.toString(), e);
    }

    return classes;
  }

}

3.0.11

package io.ray.fury.codegen;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ByteArrayClassLoader;
import org.codehaus.janino.ClassLoaderIClassLoader;
import org.codehaus.janino.Compiler;
import org.codehaus.janino.util.resource.MapResourceCreator;
import org.codehaus.janino.util.resource.MapResourceFinder;
import org.codehaus.janino.util.resource.Resource;

/**
 * A util to compile code to bytecode and create classloader to load generated class.
 * Based on org.codehaus.janino.tests.CompilerTest#testInMemoryCompilation,
 * see (https://github.com/janino-compiler/janino/issues/52)
 */
public class JaninoUtils {

  public static ClassLoader compile(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    final Map<String, byte[]> classes = toBytecode(parentClassLoader, compileUnits);
    // Set up a class loader that finds and defined the generated classes.
    return new ByteArrayClassLoader(classes, parentClassLoader);
  }

  public static List<byte[]> compileToBytecode(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    final Map<String, byte[]> classes = toBytecode(parentClassLoader, compileUnits);
    List<byte[]> byteCodes = new ArrayList<>(compileUnits.length);
    for (CompileUnit unit : compileUnits) {
      String key = unit.pkg.replace(".", "/") + "/" + unit.mainClassName + ".class";
      byteCodes.add(classes.get(key));
    }
    return byteCodes;
  }

  private static Map<String, byte[]> toBytecode(ClassLoader parentClassLoader, CompileUnit... compileUnits) {
    MapResourceFinder sourceFinder = new MapResourceFinder();
    for (CompileUnit unit : compileUnits) {
      String stubFileName = unit.pkg.replace(".", "/") + "/" + unit.mainClassName + ".java";
      sourceFinder.addResource(stubFileName, unit.getCode());

      Path path = Paths.get(CodeGenerator.getCodeDir(), stubFileName).toAbsolutePath();
      try {
        path.getParent().toFile().mkdirs();
        if (CodeGenerator.deleteCodeOnExit()) {
          path.toFile().deleteOnExit();
        }
        Files.write(path, unit.getCode().getBytes());
      } catch (IOException e) {
        throw new RuntimeException(String.format("Write code file %s failed", path), e);
      }
    }

    // Storage for generated bytecode
    final Map<String, byte[]> classes = new HashMap<>();
    // Set up the compiler.
    ClassLoaderIClassLoader iClassLoader = new ClassLoaderIClassLoader(parentClassLoader);
    Compiler compiler = new Compiler(sourceFinder, iClassLoader);
    compiler.setClassFileCreator(new MapResourceCreator(classes));
    compiler.setClassFileFinder(new MapResourceFinder(classes));

    // set debug flag to get source file names and line numbers for debug and stacktrace.
    // this is also the default behaviour for javac.
    compiler.setDebugSource(true);
    compiler.setDebugLines(true);

    // Compile all sources
    try {
      compiler.compile(sourceFinder.resources().toArray(new Resource[0]));
    } catch (CompileException | IOException e) {
      StringBuilder msgBuilder = new StringBuilder("Compile error: \n");
      for (int i = 0; i < compileUnits.length; i++) {
        CompileUnit unit = compileUnits[i];
        if (i != 0) {
          msgBuilder.append('\n');
        }
        String qualifiedName = unit.pkg + "." + unit.mainClassName;
        msgBuilder.append(qualifiedName).append(":\n");
        msgBuilder.append(CodeFormatter.format(unit.getCode()));
      }
      throw new CodegenException(msgBuilder.toString(), e);
    }

    return classes;
  }

}

Result

In my macos machine:

For 3.0.11: Total cost 98.523273 ms, average time is 1.970465 ms
For 3.1.2: Total cost 15863.328650 ms, average time is 317.266573 ms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions