[ 
https://issues.apache.org/jira/browse/STORM-2148?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15601152#comment-15601152
 ] 

Jungtaek Lim commented on STORM-2148:
-------------------------------------

I've done simple benchmark between script evaluation vs native instance method 
call.

Below is test code block, which is borrowed from TestPlanCompiler.testNested():
{code}
{
  final Object[] current = context.values;
  final java.util.Map inp2_ = (java.util.Map) current[2];
  final java.util.List inp3_ = (java.util.List) current[3];
  final boolean v4 = (Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2) == null;
  outputValues[0] = ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, "a"), "b") == 
null || ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, "a"), 
"b")).intValue() == 2) && (v4 || ((Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2)).intValue() 
== 200) ? ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, "a"), "b") == 
null || v4 ? (Boolean) null : Boolean.TRUE) : Boolean.FALSE;
}
if (outputValues[0] == null || !((Boolean) outputValues[0])) { return 0; }

{
  final Object[] current = context.values;
  final java.util.Map inp1_ = (java.util.Map) current[1];
  outputValues[0] = org.apache.calcite.runtime.SqlFunctions.toInt(current[0]);
  outputValues[1] = inp1_ == null ? (Integer) null : (Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp1_, "c");
  outputValues[2] = (java.util.Map) current[2];
  outputValues[3] = (java.util.List) current[3];
}

return 1;
{code}

and below is test benchmark code I've written. 

{code}
package redis.clients.jedis.test;

import com.google.common.collect.Lists;
import org.apache.calcite.DataContext;
import org.apache.calcite.interpreter.Context;
import org.apache.calcite.interpreter.Scalar;
import org.apache.calcite.interpreter.StormContext;
import org.apache.calcite.interpreter.StormDataContext;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ScriptEvaluator;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.junit.ContiPerfRule;
import org.databene.contiperf.report.CSVSummaryReportModule;
import org.databene.contiperf.report.HtmlReportModule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NativeVsCodeEvaluatePerformantTest {
  public static final int OPERATIONS_PER_TEST = 1000;
    public static final int TEST_DURATION_MILLIS = 600 * 1000;
    public static final int WARMUP_MILLIS = 60 * 1000;

    private ScriptEvaluator evaluator;
    private Scalar nativeClass;
    private Object[] outputValues;
    private DataContext dataContext;

    @Rule
    public ContiPerfRule rule = new ContiPerfRule(
            new HtmlReportModule(),
            new CSVSummaryReportModule());
  private StormContext calciteContext;

  public NativeVsCodeEvaluatePerformantTest() throws IOException {
        super();
    }

    @Before
    public void setUp() throws IOException, CompileException {
        String expression = "{\n" + " final Object[] current = 
context.values;\n"
            + " final java.util.Map inp2_ = (java.util.Map) current[2];\n"
            + " final java.util.List inp3_ = (java.util.List) current[3];\n"
            + " final boolean v4 = (Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2) == null;\n"
            + " outputValues[0] = ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, \"a\"), \"b\") 
== null || ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, \"a\"), 
\"b\")).intValue() == 2) && (v4 || ((Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2)).intValue() 
== 200) ? ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ? 
(java.util.Map) null : (java.util.Map) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_, \"a\"), \"b\") 
== null || v4 ? (Boolean) null : Boolean.TRUE) : Boolean.FALSE;\n"
            + "}\n" + "if (outputValues[0] == null || !((Boolean) 
outputValues[0])) { return 0; }\n"
            + "\n" + "{\n" + " final Object[] current = context.values;\n"
            + " final java.util.Map inp1_ = (java.util.Map) current[1];\n"
            + " outputValues[0] = 
org.apache.calcite.runtime.SqlFunctions.toInt(current[0]);\n"
            + " outputValues[1] = inp1_ == null ? (Integer) null : (Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp1_, \"c\");\n"
            + " outputValues[2] = (java.util.Map) current[2];\n"
            + " outputValues[3] = (java.util.List) current[3];\n" + "}\n" + 
"\n" + "return 1;\n";

        evaluator = new ScriptEvaluator(expression, int.class,
            new String[] {"context", "outputValues"},
            new Class[] { Context.class, Object[].class });

        nativeClass = new NativeClass();
        outputValues = new Object[4];
        dataContext = new StormDataContext();

      calciteContext = new StormContext(dataContext);
      calciteContext.values = getTestData();
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testEvaluate() throws IOException, InvocationTargetException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        evaluator.evaluate(new Object[] { calciteContext, outputValues });
      }
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testCallNativeClass() throws IOException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        nativeClass.execute(calciteContext, outputValues);
      }
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testEvaluate2() throws IOException, InvocationTargetException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        evaluator.evaluate(new Object[] { calciteContext, outputValues });
      }
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testCallNativeClass2() throws IOException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        nativeClass.execute(calciteContext, outputValues);
      }
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testEvaluate3() throws IOException, InvocationTargetException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        evaluator.evaluate(new Object[] { calciteContext, outputValues });
      }
    }

    @Test
    @PerfTest(duration = TEST_DURATION_MILLIS, threads = 1, warmUp = 
WARMUP_MILLIS)
    public void testCallNativeClass3() throws IOException {
      for (int i = 0 ; i < OPERATIONS_PER_TEST ; i++) {
        nativeClass.execute(calciteContext, outputValues);
      }
    }

    class NativeClass implements Scalar {

        public Object execute(Context context) {
          {
            Object[] outputValues = new Object[4];

            final Object[] current = context.values;
            final java.util.Map inp2_ = (java.util.Map) current[2];
            final java.util.List inp3_ = (java.util.List) current[3];
            final boolean v4 =
                (Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2) == null;
            outputValues[0] = ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(
                inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b") == null ||
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b")).intValue() == 2) && 
(v4 ||
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2)).intValue() 
== 200) ?
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b") == null || v4 ? 
(Boolean) null : Boolean.TRUE) :
                Boolean.FALSE;
          }
            if (outputValues[0] == null || !((Boolean) outputValues[0])) { 
return 0; }
          {
            final Object[] current = context.values;
            final java.util.Map inp1_ = (java.util.Map) current[1];
            outputValues[0] = 
org.apache.calcite.runtime.SqlFunctions.toInt(current[0]);
            outputValues[1] = inp1_ == null ?
                (Integer) null :
                (Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp1_, "c");
            outputValues[2] = (java.util.Map) current[2];
            outputValues[3] = (java.util.List) current[3];

            return outputValues;
          }
        }

        public void execute(Context context, Object[] results) {
          {
            Object[] outputValues = results;

            final Object[] current = context.values;
            final java.util.Map inp2_ = (java.util.Map) current[2];
            final java.util.List inp3_ = (java.util.List) current[3];
            final boolean v4 =
                (Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2) == null;
            outputValues[0] = ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(
                inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b") == null ||
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b")).intValue() == 2) && 
(v4 ||
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.arrayItemOptional(inp3_, 2)).intValue() 
== 200) ?
                ((Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp2_ == null ?
                    (java.util.Map) null :
                    (java.util.Map) org.apache.calcite.runtime.SqlFunctions
                        .mapItemOptional(inp2_, "a"), "b") == null || v4 ? 
(Boolean) null : Boolean.TRUE) :
                Boolean.FALSE;
          }
            if (outputValues[0] == null || !((Boolean) outputValues[0])) { 
return; }
          {
            final Object[] current = context.values;
            final java.util.Map inp1_ = (java.util.Map) current[1];
            outputValues[0] = 
org.apache.calcite.runtime.SqlFunctions.toInt(current[0]);
            outputValues[1] = inp1_ == null ?
                (Integer) null :
                (Integer) 
org.apache.calcite.runtime.SqlFunctions.mapItemOptional(inp1_, "c");
            outputValues[2] = (java.util.Map) current[2];
            outputValues[3] = (java.util.List) current[3];
          }
        }
    }

    private Object[] getTestData() {
        List<Integer> ints = Arrays.asList(100, 200, 300);
        int i = 1;
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("b", i);
        map.put("c", i*i);
        Map<String, Map<String, Integer>> mm = new HashMap<String, Map<String, 
Integer>>();
        mm.put("a", map);
        return Lists.newArrayList(i, map, mm, ints).toArray();
    }
}
{code}

3 tests are for method call, and other 3 tests are for script evaluation. I 
took median from 3 results.

|| metric || native class method call || evaluate script ||
| samples | 27438405 | 21017191 |
| max | 2 | 3 |
| average | 0.019548330159861696 | 0.025557173648942905 |

Native method call is approximately 30% faster than evaluating script.

Raw results are here:

redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testCallNativeClass
samples: 27438405
max:     2
average: 0.019548330159861696
median:  0
redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testCallNativeClass2
samples: 27677588
max:     1
average: 0.01939981186221863
median:  0
redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testCallNativeClass3
samples: 26451435
max:     18
average: 0.02029791578415311
median:  0

redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testEvaluate
samples: 21017191
max:     3
average: 0.025557173648942905
median:  0
redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testEvaluate2
samples: 21046684
max:     2
average: 0.025537039469020393
median:  0
redis.clients.jedis.test.NativeVsCodeEvaluatePerformantTest.testEvaluate3
samples: 19714388
max:     5
average: 0.02726546723134393
median:  0


> [Storm SQL] Trident mode: back to code generate and compile Trident topology
> ----------------------------------------------------------------------------
>
>                 Key: STORM-2148
>                 URL: https://issues.apache.org/jira/browse/STORM-2148
>             Project: Apache Storm
>          Issue Type: Improvement
>          Components: storm-sql
>            Reporter: Jungtaek Lim
>            Assignee: Jungtaek Lim
>
> Now Storm SQL just converts Rex to code block and pass to Evaluation~ class 
> so that each class can evaluate the code block in runtime.
> This change made the code greatly simplified, but I expect that it is not 
> same as performant as compiling and execute natively.
> Linq4j in Calcite provides utility methods to make the code nicely. It's 
> going to really really more verbose, but really better than having string 
> concatenated code block since it doesn't have any guards.
> So let's convert it back to code generation, but more elegant.



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to