There are many places in EPL that take one or more key expressions as parameters. For instance, in the group-by clause, there can be multiple expressions. The EPL compiler, starting with version 8.2.0, builds classes to represent multiple key expressions (compound keys) as they occur. This saves heap memory and improves "hashCode" and "equals" performance.

Suppose that an event has two fields that together make up the grouping key in a group-by clause. The sample schema calls the two fields 'part0' and 'part1'. The sample schema uses object-array events but the same applies to JSON, Map and all other event types. The EPL computes the count per combination of 'part0' and 'part1' key.

@public @buseventtype create objectarray schema SampleEvent(
    part0 int, part1 int);
select count(*) from SampleEvent group by part0, part1;

Internally and not visible to you the EPL compiler produces a class for the compound key that looks similar to the class here.

public class CompoundKey {
    int part0;
    int part1;

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CompoundKey that = (CompoundKey) o;
        if (part0 != that.part0) return false;
        return part1 == that.part1;
    }

    public int hashCode() {
        int result = part0;
        result = 31 * result + part1;
        return result;
    }
}

To give you an idea of the performance improvement, we ran the JMH benchmark for Esper version 8.2.0 and Esper version 8.1.0. The simulator produces 1M key combinations. The result is that version 8.2.0 is quite a bit faster.

Benchmark                Mode  Cnt       Score      Error  Units
Version_8_2_0           thrpt    5  782292.168 ± 84810.439  ops/s 
Version_8_1_0           thrpt    5  668341.421 ± 1287.599  ops/s

The JMH benchmark class is pretty plain.

public class MyBenchmark {

    @State(Scope.Thread)
    public static class MyState {

        EPRuntime runtime;
        Random random = new Random(System.currentTimeMillis());

        @Setup
        public void setUp() {
            Configuration configuration = new Configuration();
            configuration.getRuntime().getThreading().setInternalTimerEnabled(false);
            runtime = EPRuntimeProvider.getDefaultRuntime(configuration);

            String epl =
                "@public @buseventtype create objectarray schema SampleEvent(part0 int, part1 int);\n" +
                "select count(*) from SampleEvent group by part0, part1;\n";
            compileDeploy(runtime, epl);
        }
    }

    @Benchmark
    @Fork(value = 1, warmups = 0)
    public void testMethod(MyState state) {
        int part0 = next(state.random);
        int part1 = next(state.random);
        state.runtime.getEventService().sendEventObjectArray(new Object[] {part0, part1}, "SampleEvent");
    }

    private int next(Random random) {
        return random.nextInt(1000);
    }

    private static void compileDeploy(EPRuntime runtime, String epl) {
        try {
            CompilerArguments args = new CompilerArguments();
            args.getPath().add(runtime.getRuntimePath());
            EPCompiled compiled = EPCompilerProvider.getCompiler().compile(epl, args);
            runtime.getDeploymentService().deploy(compiled);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}