/* * Copyright (C) 2010 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.ast.grammar; import static org.junit.Assert.fail; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Collection; import java.util.List; import lombok.Cleanup; import lombok.ast.grammar.RunForEachFileInDirRunner.DirDescriptor; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.parser.Parser; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.parboiled.Parboiled; import org.parboiled.ReportingParseRunner; import com.google.common.collect.Lists; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.util.Context; @RunWith(RunForEachFileInDirRunner.class) public class PerformanceTest extends RunForEachFileInDirRunner.SourceFileBasedTester { private static final int REPS = 50; private static final boolean VERBOSE = System.getProperty("lombok.ast.test.verbose") != null; private static final boolean EXTENDED = System.getProperty("lombok.ast.test.extended") != null; private static final double MAX_FACTOR = 15; private static long javacTotal, lombokTotal, ecjTotal, parboiledTotal; @BeforeClass public void init() { if (VERBOSE) { System.out.printf("[%20s / %30s] Per entry: time in millis for %d reps [lombok takes X longer than ~ : ~ takes X longer than javac]\n", "path", "file", REPS); } } @AfterClass public void summary() { if (VERBOSE) { System.out.printf("[%20s / %30s] l.ast: %5d [ 1.00 : %6.02f] jc: %5d [%6.02f : 1.00] ecj: %5d [%6.02f : %6.02f] pb: %5d [%6.02f : %6.02f]\n", "", "*** TOTALS ***", lombokTotal, (double)lombokTotal / javacTotal, javacTotal, (double)lombokTotal / javacTotal, ecjTotal, (double)lombokTotal / ecjTotal, (double)ecjTotal / javacTotal, parboiledTotal, (double)lombokTotal / parboiledTotal, (double)parboiledTotal / javacTotal); } } @Override protected Collection<DirDescriptor> getDirDescriptors() { List<DirDescriptor> descriptors = Lists.newArrayList(); descriptors.add(DirDescriptor.of(new File("test/resources/idempotency"), true)); descriptors.add(DirDescriptor.of(new File("test/resources/alias"), true)); descriptors.add(DirDescriptor.of(new File("test/resources/special"), true)); if (VERBOSE) { descriptors.add(DirDescriptor.of(new File("test/resources/performance"), true)); } return descriptors; } @Test public boolean testPerformance(Source source) { if (!EXTENDED) return false; parseWithJavac(source); long takenByJavac = System.currentTimeMillis(); for (int i = 0; i < REPS; i++) { parseWithJavac(source); } takenByJavac = System.currentTimeMillis() - takenByJavac; javacTotal += takenByJavac; parseWithEcj(source); long takenByEcj = System.currentTimeMillis(); for (int i = 0; i < REPS; i++) { parseWithEcj(source); } takenByEcj = System.currentTimeMillis() - takenByEcj; ecjTotal += takenByEcj; source.parseCompilationUnit(); long takenByLombok = System.currentTimeMillis(); for (int i = 0; i < REPS; i++) { source.clear(); source.parseCompilationUnit(); } takenByLombok = System.currentTimeMillis() - takenByLombok; lombokTotal += takenByLombok; parseWithParboiled(source); long takenByParboiled = System.currentTimeMillis(); for (int i = 0; i < REPS; i++) { parseWithParboiled(source); } takenByParboiled = System.currentTimeMillis() - takenByParboiled; parboiledTotal += takenByParboiled; String fn = source.getName(); String fnPrefix, fnSuffix, fileName; { int sep = fn.lastIndexOf('/'); if (sep == -1) { fnPrefix = ""; fileName = fnSuffix = fn; } else { fnPrefix = fn.substring(0, sep); fileName = fnSuffix = fn.substring(sep + 1); } if (fnSuffix.endsWith(".java")) fnSuffix = fnSuffix.substring(0, fnSuffix.length() - ".java".length()); if (fnPrefix.length() > 20) fnPrefix = "\u2026" + fnPrefix.substring(fnPrefix.length() - 19); if (fnSuffix.length() > 30) fnSuffix = fnSuffix.substring(0, 10) + "\u2026" + fnSuffix.substring(fnSuffix.length() - 19); } if (VERBOSE) { System.out.printf("[%20s / %30s] l.ast: %5d [ 1.00 : %6.02f] jc: %5d [%6.02f : 1.00] ecj: %5d [%6.02f : %6.02f] pb: %5d [%6.02f : %6.02f]\n", fnPrefix, fnSuffix, takenByLombok, (double)takenByLombok / takenByJavac, takenByJavac, (double)takenByLombok / takenByJavac, takenByEcj, (double)takenByLombok / takenByEcj, (double)takenByEcj / takenByJavac, takenByParboiled, (double)takenByLombok / takenByParboiled, (double)takenByParboiled / takenByJavac); } double factorVsJavac = (double)takenByLombok / takenByJavac; if (factorVsJavac > MAX_FACTOR) { if (VERBOSE) { try { File reportFile = new File("test/reports/" + fileName + ".report"); reportFile.getParentFile().mkdirs(); @Cleanup FileOutputStream rawOut = new FileOutputStream(reportFile); Writer out = new BufferedWriter(new OutputStreamWriter(rawOut, "UTF-8")); out.write(String.format("Parse Profile for: %s which is slower than javac by a factor of %.02f\n", source.getName(), factorVsJavac)); for (String report : source.getDetailedProfileInformation(25)) { out.write(report); out.write("==================================="); out.write("\n"); } out.close(); rawOut.close(); System.out.println("Profile report written to: " + reportFile.getCanonicalPath()); } catch (IOException e) { System.err.println("I/O error writing profile report on " + source.getName() + "; Possibly ./test/reports is not writable?"); e.printStackTrace(); } } fail(String.format("Performance is slower than javac by factor %d on %s", (int)factorVsJavac, source.getName())); } return true; } private void parseWithParboiled(Source source) { if (VERBOSE) { ParboiledJavaGrammar parser = Parboiled.createParser(ParboiledJavaGrammar.class); ReportingParseRunner.run(parser.CompilationUnit(), source.getRawInput()); } } private void parseWithJavac(Source source) { Context context = new Context(); JavaCompiler compiler = new JavaCompiler(context); compiler.genEndPos = true; compiler.parse(new ContentBasedJavaFileObject(source.getName(), source.getRawInput())); } protected CompilerOptions ecjCompilerOptions() { CompilerOptions options = new CompilerOptions(); options.complianceLevel = ClassFileConstants.JDK1_6; options.sourceLevel = ClassFileConstants.JDK1_6; options.targetJDK = ClassFileConstants.JDK1_6; options.parseLiteralExpressionsAsConstants = true; return options; } private void parseWithEcj(Source source) { if (VERBOSE) { CompilerOptions compilerOptions = ecjCompilerOptions(); Parser parser = new Parser(new ProblemReporter( DefaultErrorHandlingPolicies.proceedWithAllProblems(), compilerOptions, new DefaultProblemFactory() ), compilerOptions.parseLiteralExpressionsAsConstants); parser.javadocParser.checkDocComment = true; CompilationUnit sourceUnit = new CompilationUnit(source.getRawInput().toCharArray(), source.getName(), "UTF-8"); CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0); parser.parse(sourceUnit, compilationResult); } } }