You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by jf...@apache.org on 2022/01/02 09:54:17 UTC

[iotdb] branch experimental/code-generation updated: Added Code Generation to DataSetWithTimeGenerator. Added extensive Benchmarking

This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch experimental/code-generation
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/experimental/code-generation by this push:
     new 6025be9  Added Code Generation to DataSetWithTimeGenerator. Added extensive Benchmarking
6025be9 is described below

commit 6025be924cefc2a9f7865e618a98c3a398930e67
Author: julian <j....@pragmaticminds.de>
AuthorDate: Sun Jan 2 10:53:41 2022 +0100

    Added Code Generation to DataSetWithTimeGenerator.
    Added extensive Benchmarking
---
 .../query/dataset/DataSetWithTimeGenerator.java    | 298 +++++++++++++++++--
 ...rPerformanceTest.java => CodeGenBenchmark.java} | 329 +++++++++++----------
 2 files changed, 442 insertions(+), 185 deletions(-)

diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/query/dataset/DataSetWithTimeGenerator.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/query/dataset/DataSetWithTimeGenerator.java
index 1773c66..d381545 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/query/dataset/DataSetWithTimeGenerator.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/query/dataset/DataSetWithTimeGenerator.java
@@ -18,15 +18,37 @@
  */
 package org.apache.iotdb.tsfile.read.query.dataset;
 
+import org.apache.calcite.linq4j.tree.BlockStatement;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.MethodDeclaration;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
+import org.apache.calcite.linq4j.tree.Statement;
+import org.apache.calcite.linq4j.tree.Types;
+import org.apache.iotdb.tsfile.exception.NotImplementedException;
+import org.apache.iotdb.tsfile.exception.write.UnSupportedDataTypeException;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.Field;
 import org.apache.iotdb.tsfile.read.common.Path;
 import org.apache.iotdb.tsfile.read.common.RowRecord;
 import org.apache.iotdb.tsfile.read.query.timegenerator.TimeGenerator;
 import org.apache.iotdb.tsfile.read.reader.series.FileSeriesReaderByTimestamp;
 import org.apache.iotdb.tsfile.utils.TsPrimitiveType;
+import org.codehaus.commons.compiler.CompileException;
+import org.codehaus.janino.ClassBodyEvaluator;
+import org.codehaus.janino.Scanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * query processing: (1) generate time by series that has filter (2) get value of series that does
@@ -34,18 +56,30 @@ import java.util.List;
  */
 public class DataSetWithTimeGenerator extends QueryDataSet {
 
+  private static final Logger logger = LoggerFactory.getLogger(DataSetWithTimeGenerator.class);
+
+  public static final AtomicBoolean generate = new AtomicBoolean(true);
+  private static final AtomicLong classId = new AtomicLong(0);
+
+  private final RecordGenerator generator;
   private TimeGenerator timeGenerator;
   private List<FileSeriesReaderByTimestamp> readers;
   private List<Boolean> cached;
 
+  public static interface RecordGenerator {
+
+    public void accept(RowRecord record, long timestamp);
+
+  }
+
   /**
    * constructor of DataSetWithTimeGenerator.
    *
-   * @param paths paths in List structure
-   * @param cached cached boolean in List(boolean) structure
-   * @param dataTypes TSDataTypes in List structure
+   * @param paths         paths in List structure
+   * @param cached        cached boolean in List(boolean) structure
+   * @param dataTypes     TSDataTypes in List structure
    * @param timeGenerator TimeGenerator object
-   * @param readers readers in List(FileSeriesReaderByTimestamp) structure
+   * @param readers       readers in List(FileSeriesReaderByTimestamp) structure
    */
   public DataSetWithTimeGenerator(
       List<Path> paths,
@@ -57,6 +91,237 @@ public class DataSetWithTimeGenerator extends QueryDataSet {
     this.cached = cached;
     this.timeGenerator = timeGenerator;
     this.readers = readers;
+
+    if (DataSetWithTimeGenerator.generate.get()) {
+      // Do fancy stuff here, code was
+//    FileSeriesReaderByTimestamp fileSeriesReaderByTimestamp = readers.get(i);
+//    Object value = fileSeriesReaderByTimestamp.getValueInTimestamp(timestamp);
+//    if (dataTypes.get(i) == TSDataType.VECTOR) {
+//      TsPrimitiveType v = ((TsPrimitiveType[]) value)[0];
+//      rowRecord.addField(v.getValue(), v.getDataType());
+//    } else {
+//      rowRecord.addField(value, dataTypes.get(i));
+//    }
+      ParameterExpression readers_ = Expressions.parameter(new Types.ArrayType(FileSeriesReaderByTimestamp.class), "readers");
+      ParameterExpression rowRecord_ = Expressions.parameter(RowRecord.class, "rowRecord");
+      ParameterExpression timestamp_ = Expressions.parameter(long.class, "timestamp");
+
+
+      ArrayList<Statement> body = new ArrayList<>();
+      Method getValueInTimestamp;
+      Method addField;
+      Method getValue;
+      Method getDataType;
+      try {
+        getValueInTimestamp = FileSeriesReaderByTimestamp.class.getMethod("getValueInTimestamp", long.class);
+        addField = RowRecord.class.getMethod("addField", Field.class);
+        getValue = TsPrimitiveType.class.getMethod("getValue");
+        getDataType = TsPrimitiveType.class.getMethod("getDataType");
+      } catch (NoSuchMethodException e) {
+        throw new IllegalStateException();
+      }
+
+      ParameterExpression reader_ = Expressions.parameter(FileSeriesReaderByTimestamp.class, "reader");
+      ParameterExpression value_ = Expressions.parameter(Object.class, "value");
+
+      body.add(Expressions.declare(0, reader_, null));
+      body.add(Expressions.declare(0, value_, null));
+
+      for (int i = 0; i < paths.size(); i++) {
+        if (this.cached.get(i) == false) {
+          body.add(
+              Expressions.statement(
+                  Expressions.assign(reader_,
+                      Expressions.convert_(
+                          Expressions.arrayIndex(readers_, Expressions.constant(i)),
+                          FileSeriesReaderByTimestamp.class
+                      )
+                  )
+              )
+          );
+          body.add(
+              Expressions.statement(
+                  Expressions.assign(value_, Expressions.call(reader_, getValueInTimestamp, timestamp_))
+              )
+          );
+          if (dataTypes.get(i) == TSDataType.VECTOR) {
+            ParameterExpression v_ = Expressions.parameter(TsPrimitiveType.class, "v");
+            body.add(
+                Expressions.declare(1, v_,
+                    Expressions.convert_(
+                        Expressions.arrayIndex(v_, Expressions.constant(0)),
+                        TsPrimitiveType.class
+                    )
+                )
+            );
+            body.add(
+                Expressions.statement(
+                    Expressions.call(
+                        rowRecord_, addField,
+                        Expressions.call(v_, getValue),
+                        Expressions.call(v_, getDataType)
+                    )
+                )
+            );
+          } else {
+            // We can get rid of the switch statement here in addField
+            ParameterExpression field_ = Expressions.parameter(Field.class, "field" + i);
+
+            // From Field.getField(xxx)
+            // TODO add Null Check
+//            if (value == null) {
+//              return null;
+//            }
+
+
+//            Field field = new Field(dataType);
+            body.add(
+                Expressions.declare(0, field_, Expressions.new_(Field.class, Expressions.constant(dataTypes.get(i))))
+            );
+            switch (dataTypes.get(i)) {
+              case INT32:
+                // field.setIntV((int) value);
+                body.add(
+                    Expressions.statement(
+                        Expressions.call(field_, "setIntV",
+                            Expressions.unbox(
+                            Expressions.convert_(value_, Integer.class)
+                            )
+                        )
+                    )
+                );
+                break;
+//              case INT64:
+//                field.setLongV((long) value);
+//                break;
+              case FLOAT:
+//                field.setFloatV((float) value);
+                body.add(
+                    Expressions.statement(
+                        Expressions.call(field_, "setFloatV",
+                            Expressions.unbox(
+                            Expressions.convert_(value_, Float.class)
+                            )
+                        )
+                    )
+                );
+                break;
+//              case DOUBLE:
+//                field.setDoubleV((double) value);
+//                break;
+//              case BOOLEAN:
+//                field.setBoolV((boolean) value);
+//                break;
+//              case TEXT:
+//                field.setBinaryV((Binary) value);
+//                break;
+              default:
+                throw new UnSupportedDataTypeException(dataTypes.get(i).toString());
+            }
+
+
+            // Old ("inefficient") method
+//            body.add(
+//                Expressions.statement(
+//                    Expressions.call(
+//                        rowRecord_, addField,
+//                        value_,
+//                        Expressions.constant(dataTypes.get(i))
+//                    )
+//                )
+//            );
+
+            // Efficient method as we avoid a switch in the Field::addField method
+            body.add(
+                Expressions.statement(
+                    Expressions.call(
+                        rowRecord_, addField,
+                        field_
+                    )
+                )
+            );
+          }
+        } else {
+          throw new NotImplementedException();
+        }
+      }
+
+      BlockStatement block = Expressions.block(
+          Expressions.tryCatch(
+              Expressions.block(body),
+              Expressions.catch_(
+                  Expressions.parameter(IOException.class, "e"),
+                  Expressions.block()
+              )
+          )
+      );
+
+      MethodDeclaration method = Expressions.methodDecl(1, void.class, "accept", Arrays.asList(rowRecord_, timestamp_), block);
+
+      String s = Expressions.toString(method);
+
+      String className = "Generator" + classId.getAndIncrement();
+
+      String s2 = "import org.apache.iotdb.tsfile.read.reader.series.FileSeriesReaderByTimestamp;\n" +
+          "import java.util.List;" +
+          "\nprivate final FileSeriesReaderByTimestamp[] readers;\n" +
+          "public " + className + "(List<FileSeriesReaderByTimestamp> readers) {\nthis.readers = (FileSeriesReaderByTimestamp[])readers.toArray(new FileSeriesReaderByTimestamp[]{});\n}\n\n" + s;
+
+      logger.debug(s2);
+
+      RecordGenerator f;
+
+      try {
+        Scanner scanner = new Scanner("", new StringReader(s2));
+        ClassBodyEvaluator ev = new ClassBodyEvaluator(scanner, className, null, new Class[]{RecordGenerator.class}, null);
+
+        f = (RecordGenerator) ev.getClazz().getConstructors()[0].newInstance(readers);
+
+      } catch (CompileException | IllegalAccessException | InstantiationException | InvocationTargetException | IOException ex) {
+        ex.printStackTrace();
+        throw new IllegalStateException();
+      }
+
+      this.generator = f;
+    } else {
+      this.generator = new RecordGenerator() {
+        @Override
+        public void accept(RowRecord record, long timestamp) {
+          try {
+            for (int i = 0; i < paths.size(); i++) {
+
+              // get value from readers in time generator
+              if (cached.get(i)) {
+                Object value = null;
+
+                value = timeGenerator.getValue(paths.get(i));
+
+                if (dataTypes.get(i) == TSDataType.VECTOR) {
+                  TsPrimitiveType v = ((TsPrimitiveType[]) value)[0];
+                  record.addField(v.getValue(), v.getDataType());
+                } else {
+                  record.addField(value, dataTypes.get(i));
+                }
+                continue;
+              }
+
+
+              // get value from series reader without filter
+              FileSeriesReaderByTimestamp fileSeriesReaderByTimestamp = readers.get(i);
+              Object value = fileSeriesReaderByTimestamp.getValueInTimestamp(timestamp);
+              if (dataTypes.get(i) == TSDataType.VECTOR) {
+                TsPrimitiveType v = ((TsPrimitiveType[]) value)[0];
+                record.addField(v.getValue(), v.getDataType());
+              } else {
+                record.addField(value, dataTypes.get(i));
+              }
+            }
+          } catch (IOException e) {
+            throw new IllegalStateException();
+          }
+        }
+      };
+    }
   }
 
   @Override
@@ -69,30 +334,7 @@ public class DataSetWithTimeGenerator extends QueryDataSet {
     long timestamp = timeGenerator.next();
     RowRecord rowRecord = new RowRecord(timestamp);
 
-    for (int i = 0; i < paths.size(); i++) {
-
-      // get value from readers in time generator
-      if (cached.get(i)) {
-        Object value = timeGenerator.getValue(paths.get(i));
-        if (dataTypes.get(i) == TSDataType.VECTOR) {
-          TsPrimitiveType v = ((TsPrimitiveType[]) value)[0];
-          rowRecord.addField(v.getValue(), v.getDataType());
-        } else {
-          rowRecord.addField(value, dataTypes.get(i));
-        }
-        continue;
-      }
-
-      // get value from series reader without filter
-      FileSeriesReaderByTimestamp fileSeriesReaderByTimestamp = readers.get(i);
-      Object value = fileSeriesReaderByTimestamp.getValueInTimestamp(timestamp);
-      if (dataTypes.get(i) == TSDataType.VECTOR) {
-        TsPrimitiveType v = ((TsPrimitiveType[]) value)[0];
-        rowRecord.addField(v.getValue(), v.getDataType());
-      } else {
-        rowRecord.addField(value, dataTypes.get(i));
-      }
-    }
+    this.generator.accept(rowRecord, timestamp);
 
     return rowRecord;
   }
diff --git a/tsfile/src/test/java/org/apache/iotdb/tsfile/write/TsFilterPerformanceTest.java b/tsfile/src/test/java/org/apache/iotdb/tsfile/write/CodeGenBenchmark.java
similarity index 60%
rename from tsfile/src/test/java/org/apache/iotdb/tsfile/write/TsFilterPerformanceTest.java
rename to tsfile/src/test/java/org/apache/iotdb/tsfile/write/CodeGenBenchmark.java
index 3d835ca..22224e1 100644
--- a/tsfile/src/test/java/org/apache/iotdb/tsfile/write/TsFilterPerformanceTest.java
+++ b/tsfile/src/test/java/org/apache/iotdb/tsfile/write/CodeGenBenchmark.java
@@ -37,6 +37,7 @@ import org.apache.iotdb.tsfile.read.filter.ValueFilter;
 import org.apache.iotdb.tsfile.read.filter.basic.Filter;
 import org.apache.iotdb.tsfile.read.filter.codegen.Generator;
 import org.apache.iotdb.tsfile.read.filter.factory.FilterFactory;
+import org.apache.iotdb.tsfile.read.query.dataset.DataSetWithTimeGenerator;
 import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
 import org.apache.iotdb.tsfile.utils.FilePathUtils;
 import org.apache.iotdb.tsfile.utils.TsFileGeneratorForTest;
@@ -47,13 +48,20 @@ import org.apache.iotdb.tsfile.write.schema.UnaryMeasurementSchema;
 import org.junit.Assert;
 import org.junit.Test;
 import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
 import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
 import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
 import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.results.RunResult;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.format.OutputFormat;
+import org.openjdk.jmh.runner.format.OutputFormatFactory;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.VerboseMode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -61,26 +69,50 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+/**
+ * This class is a Benchmarking Tool for the evaluation of code generation.
+ * It makes heavy use of JMH.
+ *
+ * To start the Benchmarking, just run the main class.
+ */
 @State(Scope.Benchmark)
-public class TsFilterPerformanceTest {
+public class CodeGenBenchmark {
+
+  private static Logger logger = LoggerFactory.getLogger(CodeGenBenchmark.class);
+
+  @Param("standard")
+  public String filter;
+
+  @Param("1")
+  public int runs;
+
+//  @Param("false")
+//  public boolean optimizeFilters;
+//
+//  @Param("false")
+//  public boolean generateSeries;
 
-  private static Logger logger = LoggerFactory.getLogger(TsFilterPerformanceTest.class);
+  @Param("false")
+  public boolean optimize;
 
-  public static int runs = 1;
+  @Param("100000000")
+  public int datapoints;
 
-  TsFileWriter writer = null;
   String fileName = TsFileGeneratorForTest.getTestTsFilePath("root.sg1", 0, 0, 1);
   boolean closed = false;
 
-  String getFilename() {
+  static String getFilename() {
     String filePath =
         String.format(
             "/tmp/root.sg1/0/0/",
@@ -95,115 +127,32 @@ public class TsFilterPerformanceTest {
     return filePath.concat(fileName);
   }
 
-  /**
-   * Benchmark                                     Mode  Cnt      Score     Error  Units
-   * TsFilterPerformanceTest.getSimpleUnoptimized  avgt   15  10699,176 ± 511,309  ms/op
-   * @throws IOException
-   */
-  @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getSimpleUnoptimized() throws IOException {
-    List<Filter> filters = getSimpleFilters();
-    runTests(filters, false);
-  }
-
-  /**
-   * Benchmark                                   Mode  Cnt      Score     Error  Units
-   * TsFilterPerformanceTest.getSimpleOptimized  avgt   15  11084,838 ± 331,142  ms/op
-   * @throws IOException
-   */
   @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getSimpleOptimized() throws IOException {
-    List<Filter> filters = getSimpleFilters();
-    runTests(filters, true);
-  }
-
-
-  /**
-   * Benchmark                                       Mode  Cnt      Score     Error  Units
-   * TsFilterPerformanceTest.getStandardUnoptimized  avgt   15  10345,496 ± 571,557  ms/op
-   * @throws IOException
-   */
-  @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getStandardUnoptimized() throws IOException {
-    List<Filter> filters = getStandardFilters();
-    runTests(filters, false);
-  }
-
-  /**
-   * Benchmark                                     Mode  Cnt     Score     Error  Units
-   * TsFilterPerformanceTest.getStandardOptimized  avgt   15  9864,581 ± 591,591  ms/op
-   * -> 4.6%
-   * @throws IOException
-   */
-  @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getStandardOptimized() throws IOException {
-    List<Filter> filters = getStandardFilters();
-    runTests(filters, true);
-  }
-
-  /**
-   * Benchmark                                      Mode  Cnt      Score     Error  Units
-   * TsFilterPerformanceTest.getComplexUnoptimized  avgt   15  15053,562 ± 115,997  ms/op
-   * -> 12.3%
-   * @throws IOException
-   */
-  @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getComplexUnoptimized() throws IOException {
-    List<Filter> filters = getComplexFilters();
-    runTests(filters, false);
-  }
-
-  /**
-   * Benchmark                                    Mode  Cnt      Score     Error  Units
-   * TsFilterPerformanceTest.getComplexOptimized  avgt   15  13196,650 ± 477,061  ms/op
-   * @throws IOException
-   */
-  @Benchmark
-  @BenchmarkMode(Mode.AverageTime)
-  @OutputTimeUnit(TimeUnit.MILLISECONDS)
-  @Warmup(iterations = 3)
-  @Fork(3)
-  public void getComplexOptimized() throws IOException {
-    List<Filter> filters = getComplexFilters();
-    runTests(filters, true);
+  public void runBenchmark(Blackhole blackhole) throws IOException {
+    List<Filter> filters = getFilter();
+    // set optimizing parameters
+    Generator.active.set(this.optimize);
+    DataSetWithTimeGenerator.generate.set(this.optimize);
+    runTests(filters, blackhole);
   }
 
   @Test
   public void generate() throws IOException, WriteProcessException {
+    generate(100_000_000);
+  }
+
+  public static void generate(int datapoints) throws IOException, WriteProcessException {
     String filename = getFilename();
 
-    System.out.println("Writing to " + filename);
+    logger.info("Writing {} datapoints to {}", datapoints, filename);
     File f = new File(filename);
     if (!f.getParentFile().exists()) {
       Assert.assertTrue(f.getParentFile().mkdirs());
     }
-    writer = new TsFileWriter(f);
-    registerTimeseries();
+    TsFileWriter writer = new TsFileWriter(f);
+    registerTimeseries(writer);
 
-    for (long t = 0; t <= 100_000_000; t++) {
-      if (t % 100 == 0) {
-        System.out.println(t);
-      }
+    for (long t = 0; t <= datapoints; t++) {
       // normal
       TSRecord record = new TSRecord(t, "d1");
       record.addTuple(new FloatDataPoint("s1", (float) (100.0 * Math.random())));
@@ -214,56 +163,12 @@ public class TsFilterPerformanceTest {
     writer.close();
   }
 
+  private double runTests(List<Filter> filter, Blackhole blackhole) throws IOException {
 
-  /**
-   * Without optimization: 10798.055595900001 ms
-   * With: 9907.020887499999
-   * Improvement ~ 8%
-   */
-  @Test
-  public void readMany() throws IOException {
-    List<List<Filter>> scenarios = Arrays.asList(
-        getSimpleFilters(),
-        getStandardFilters(),
-        getComplexFilters()
-    );
-
-    ArrayList<Double> results = new ArrayList<>();
-
-    for (int scenarioCount = 0; scenarioCount < scenarios.size(); scenarioCount++) {
-      System.out.println("Starting Scenario " + scenarioCount);
-      List<Filter> filter = scenarios.get(scenarioCount);
-
-      // First, no optimizer
-      double noOptimizer = runTests(filter, false);
-      double optimizer = runTests(filter, true);
-      double improvement = 100.0 * (1 - optimizer / noOptimizer);
-
-      System.out.println("==========================");
-      System.out.println("Scenario " + scenarioCount);
-      System.out.println("==========================");
-      System.out.printf("Duration (no optimizer): %.2f ms\n", noOptimizer);
-      System.out.printf("Duration (optimizer): %.2f ms\n", optimizer);
-      System.out.printf("Improvement: %.2f %%\n\n", improvement);
-
-      results.add(improvement);
-    }
-
-    System.out.println("==========================");
-    System.out.println("Final Result");
-    System.out.println("==========================");
-    for (Double result : results) {
-      System.out.printf("Improvement: %.2f %%\n", result);
-    }
-    System.out.println("==========================");
-  }
-
-  private double runTests(List<Filter> filter, boolean optimizer) throws IOException {
-    Generator.active.set(optimizer);
     long start = System.nanoTime();
     for (int i = 1; i <= runs; i++) {
       logger.info("Round " + i);
-      read(filter);
+      read(filter, blackhole);
     }
     long end = System.nanoTime();
 
@@ -283,6 +188,17 @@ public class TsFilterPerformanceTest {
     return filters;
   }
 
+  private List<Filter> getFilter() {
+    if ("standard".equals(this.filter)){
+      return getStandardFilters();
+    } else if ("simple".equals(this.filter)) {
+      return getSimpleFilters();
+    } else if ("complex".equals(this.filter)) {
+      return getComplexFilters();
+    }
+    throw new IllegalStateException();
+  }
+
   private List<Filter> getStandardFilters() {
     Filter filter = TimeFilter.lt(50_000_000);
     Filter filter2 = FilterFactory.and(ValueFilter.gt(25), ValueFilter.lt(75));
@@ -315,7 +231,7 @@ public class TsFilterPerformanceTest {
     return filters;
   }
 
-  public void read(List<Filter> filters) throws IOException {
+  public void read(List<Filter> filters, Blackhole blackhole) throws IOException {
     TsFileSequenceReader fileSequenceReader = new TsFileSequenceReader(getFilename());
     TsFileReader fileReader = new TsFileReader(fileSequenceReader);
 
@@ -343,13 +259,14 @@ public class TsFilterPerformanceTest {
     int count = 0;
     while (dataSet.hasNext()) {
       RowRecord rowRecord = dataSet.next();
+      blackhole.consume(rowRecord);
       count++;
     }
 
     logger.debug("Iterartion done, " + count + " points");
   }
 
-  private void registerTimeseries() {
+  private static void registerTimeseries(TsFileWriter writer) {
     // register nonAligned timeseries "d1.s1","d1.s2","d1.s3"
     try {
       writer.registerTimeseries(
@@ -428,4 +345,102 @@ public class TsFilterPerformanceTest {
     }*/
   }
 
+  @Setup(Level.Trial)
+  public void setup() throws IOException, WriteProcessException {
+    generate(datapoints);
+  }
+
+  /**
+   * Benchmark                             (filter)  (generateSeries)  (optimizeFilters)  (runs)  Mode  Cnt      Score       Error  Units
+   * TsFilterPerformanceTest.runBenchmark  standard             false              false       1  avgt    5   8665,837 ±   703,708  ms/op
+   * TsFilterPerformanceTest.runBenchmark  standard             false              false       5  avgt    5  43861,836 ±  8981,774  ms/op
+   * TsFilterPerformanceTest.runBenchmark  standard             false              false      10  avgt    5  88036,981 ± 13661,095  ms/op
+   * @param args
+   * @throws RunnerException
+   */
+  public static void main(String[] args) throws RunnerException, IOException, WriteProcessException {
+      // Start with the execution
+      OptionsBuilder optionsBuilder = new OptionsBuilder();
+      optionsBuilder
+          .include("runBenchmark")
+          .forks(1)
+          .measurementIterations(3)
+          .warmupIterations(3)
+          .mode(Mode.AverageTime)
+          .timeUnit(TimeUnit.MILLISECONDS)
+          .param("datapoints", "100", "10000", "1000000", "100000000", "1000000000")
+          .param("optimize", "false", "true")
+          .param("filter", "simple", "standard", "complex")
+      //        .param("runs", "1")
+      ;
+      OutputFormat outputFormat = OutputFormatFactory.createFormatInstance(System.out, VerboseMode.NORMAL);
+      Runner runner = new Runner(optionsBuilder.build(), outputFormat);
+
+      Collection<RunResult> results = runner.run();
+
+      printResults(results);
+  }
+
+  private static void printResults(Collection<RunResult> results) {
+    // Prepare Output for csv
+    StringBuilder sb = new StringBuilder();
+    RunResult first = results.iterator().next();
+
+    ArrayList<String> orderedParameters = new ArrayList<>(first.getParams().getParamsKeys());
+
+    for (RunResult result : results) {
+      System.out.println("\n===================");
+      for (String paramsKey : result.getParams().getParamsKeys()) {
+        String paramValue = result.getParams().getParam(paramsKey);
+        System.out.printf("%s: %s\n", paramsKey, paramValue);
+      }
+
+      // Now calculate the baseline
+      double duration = result.getPrimaryResult().getScore();
+
+      // Now find the score of the base run with this parameters
+      Optional<RunResult> baseline = findBaselineForResult(results, result);
+
+      if (!baseline.isPresent()) {
+        System.out.println(" -- no baseline found -- ");
+        continue;
+      }
+
+      double baseLineDuration = baseline.get().getPrimaryResult().getScore();
+
+      double improvement = 100.0 * (1 - duration/baseLineDuration);
+
+      String scoreUnit = result.getPrimaryResult().getScoreUnit();
+      System.out.printf("Result: %.2f %s\n", duration, scoreUnit);
+      System.out.printf("Baseline: %.2f %s\n", baseLineDuration, scoreUnit);
+      System.out.printf("Improvement: %.2f %%\n", improvement);
+
+
+      // Create all Parameters
+      String prefix = orderedParameters.stream().map(key -> result.getParams().getParam(key)).collect(Collectors.joining(";"));
+
+      sb.append(String.format(Locale.ENGLISH, "%s;%f;%f;%f\n", prefix, duration, baseLineDuration, improvement/100.0));
+    }
+
+    System.out.println("Final Result");
+    System.out.println("==========");
+    System.out.println(sb);
+  }
+
+  private static Optional<RunResult> findBaselineForResult(Collection<RunResult> results, RunResult result) {
+    Optional<RunResult> baseline = results.stream().filter(new Predicate<RunResult>() {
+      @Override
+      public boolean test(RunResult runResult) {
+        boolean baseline = "false".equals(runResult.getParams().getParam("optimize"));
+
+        Set<String> compareKeys = result.getParams().getParamsKeys().stream().filter(key -> !Arrays.asList("optimize").contains(key)).collect(Collectors.toSet());
+
+        boolean same = compareKeys.stream().allMatch(key -> result.getParams().getParam(key).equals(runResult.getParams().getParam(key)));
+
+        return same && baseline;
+      }
+    }).findAny();
+    return baseline;
+  }
+
 }