JFlexBench.java

/*
 * Copyright (c) 2020, Gerwin Klein
 * SPDX-License-Identifier: BSD-3-Clause
 */

package jflex.benchmark;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.concurrent.TimeUnit;
import jflex.benchmark.pregen.NoAction17;
import jflex.benchmark.pregen.NoAction18;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@BenchmarkMode({Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime})
// @BenchmarkMode({Mode.AverageTime, Mode.SingleShotTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 3)
public class JFlexBench {

  @State(Scope.Benchmark)
  public static class LexerState {
    /**
     * Factor by which to scale the input size. We should see a benchmark time roughly linear in the
     * factor, i.e. the first time times 10 and 100.
     */
    @Param({"1000", "10000"})
    public int factor;

    @Param({"1", "2"})
    public int input;

    /** The length of the input for the benchmark. We give this to the baseline, but not JFlex. */
    public int length;

    /** The reader the input will be read from. Must support {@code reset()}. */
    public Reader reader;

    /** Create input and populate state fields. Runs once per entire benchmark. */
    @Setup
    public void setup() {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < 10 * factor; i++) {
        switch (input) {
          case 1:
            builder.append("aaa");
            builder.append("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
            builder.append("  ");
            break;
          case 2:
            builder.append("����a");
            builder.append("���������������������������������bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
            builder.append("  ");
            break;
          default:
            assert false : "reached unreachable default case";
        }
      }
      length = builder.length();
      reader = new StringReader(builder.toString());
    }
  }

  /**
   * Benchmark subject generated by current JFlex snapshot.
   *
   * @param state benchmark state parameter. Includes input, factor, etc.
   * @return number of matches
   * @throws IOException if anything goes wrong in the lexer (should not occur)
   */
  @Benchmark
  public int noActionLexer(LexerState state) throws IOException {
    state.reader.reset();
    return new NoAction(state.reader).yylex();
  }

  /**
   * Benchmark subject generated by JFlex 1.7.0
   *
   * @param state benchmark state parameter. Includes input, factor, etc.
   * @return number of matches
   * @throws IOException if anything goes wrong in the lexer (should not occur)
   */
  @Benchmark
  public int noAction17Lexer(LexerState state) throws IOException {
    state.reader.reset();
    return new NoAction17(state.reader).yylex();
  }

  /**
   * Benchmark subject generated by JFlex 1.8.2
   *
   * @param state benchmark state parameter. Includes input, factor, etc.
   * @return number of matches
   * @throws IOException if anything goes wrong in the lexer (should not occur)
   */
  @Benchmark
  public int noAction18Lexer(LexerState state) throws IOException {
    state.reader.reset();
    return new NoAction18(state.reader).yylex();
  }

  /**
   * The base line: a single continuous pass accessing each character once, through a buffer filled
   * by a reader in one single reader invocation.
   */
  // uncomment to enable baseline
  // @Benchmark
  public void baselineReader(LexerState state, Blackhole bh) throws IOException {
    char[] buffer = new char[state.length];
    state.reader.reset();
    state.reader.read(buffer, 0, buffer.length);
    for (int i = 0; i < buffer.length; i++) {
      bh.consume(buffer[i]);
    }
  }

  public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(JFlexBench.class.getSimpleName()).build();

    new Runner(opt).run();
  }
}