-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ES|QL deserves a new hash table #98749
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.benchmark.compute.operator; | ||
|
||
import org.apache.lucene.util.BytesRef; | ||
import org.elasticsearch.common.breaker.NoopCircuitBreaker; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.util.BigArrays; | ||
import org.elasticsearch.common.util.BytesRefHash; | ||
import org.elasticsearch.common.util.LongHash; | ||
import org.elasticsearch.common.util.LongLongHash; | ||
import org.elasticsearch.common.util.LongObjectPagedHashMap; | ||
import org.elasticsearch.common.util.PageCacheRecycler; | ||
import org.elasticsearch.compute.aggregation.blockhash.Ordinator64; | ||
import org.openjdk.jmh.annotations.Benchmark; | ||
import org.openjdk.jmh.annotations.BenchmarkMode; | ||
import org.openjdk.jmh.annotations.Fork; | ||
import org.openjdk.jmh.annotations.Measurement; | ||
import org.openjdk.jmh.annotations.Mode; | ||
import org.openjdk.jmh.annotations.OperationsPerInvocation; | ||
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 java.util.concurrent.TimeUnit; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.LongStream; | ||
|
||
@Warmup(iterations = 5) | ||
@Measurement(iterations = 7) | ||
@BenchmarkMode(Mode.AverageTime) | ||
@OutputTimeUnit(TimeUnit.NANOSECONDS) | ||
@State(Scope.Thread) | ||
@Fork(value = 1, jvmArgsAppend = { "--enable-preview", "--add-modules", "jdk.incubator.vector" }) | ||
public class HashBenchmark { | ||
static { | ||
// Smoke test all the expected values and force loading subclasses more like prod | ||
try { | ||
for (String unique : HashBenchmark.class.getField("unique").getAnnotationsByType(Param.class)[0].value()) { | ||
HashBenchmark bench = new HashBenchmark(); | ||
bench.unique = Integer.parseInt(unique); | ||
bench.initTestData(); | ||
bench.longHash(); | ||
bench.bytesRefHash(); | ||
bench.longLongHash(); | ||
bench.longObjectHash(); | ||
bench.ordinator(); | ||
bench.ordinatorArray(); | ||
} | ||
} catch (NoSuchFieldException e) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
|
||
private static final int ITERATIONS = 10_000_000; | ||
|
||
@Param({ "5", "1000", "10000", "100000", "1000000" }) | ||
public int unique; | ||
|
||
private long[] testLongs; | ||
private BytesRef[] testBytes; | ||
private int[] targetInts; | ||
private long[] targetLongs; | ||
private Object[] targetObject; | ||
|
||
@Setup | ||
public void initTestData() { | ||
testLongs = LongStream.range(0, ITERATIONS).map(l -> l % unique).toArray(); | ||
BytesRef[] uniqueBytes = IntStream.range(0, unique).mapToObj(i -> new BytesRef(Integer.toString(i))).toArray(BytesRef[]::new); | ||
testBytes = IntStream.range(0, ITERATIONS).mapToObj(i -> uniqueBytes[i % unique]).toArray(BytesRef[]::new); | ||
targetInts = new int[ITERATIONS]; | ||
targetLongs = new long[ITERATIONS]; | ||
targetObject = new Object[ITERATIONS]; | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void longHash() { | ||
LongHash hash = new LongHash(16, BigArrays.NON_RECYCLING_INSTANCE); | ||
for (int i = 0; i < testLongs.length; i++) { | ||
targetLongs[i] = hash.add(testLongs[i]); | ||
} | ||
if (hash.size() != unique) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void bytesRefHash() { | ||
BytesRefHash hash = new BytesRefHash(16, BigArrays.NON_RECYCLING_INSTANCE); | ||
for (int i = 0; i < testLongs.length; i++) { | ||
targetLongs[i] = hash.add(testBytes[i]); | ||
} | ||
if (hash.size() != unique) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void longLongHash() { | ||
LongLongHash hash = new LongLongHash(16, BigArrays.NON_RECYCLING_INSTANCE); | ||
for (int i = 0; i < testLongs.length; i++) { | ||
targetLongs[i] = hash.add(testLongs[i], testLongs[i]); | ||
} | ||
if (hash.size() != unique) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void longObjectHash() { | ||
LongObjectPagedHashMap<Object> hash = new LongObjectPagedHashMap<>(16, BigArrays.NON_RECYCLING_INSTANCE); | ||
Object o = new Object(); | ||
for (int i = 0; i < testLongs.length; i++) { | ||
targetObject[i] = hash.put(testLongs[i], o); | ||
} | ||
if (hash.size() != unique) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void ordinator() { | ||
Ordinator64 hash = new Ordinator64( | ||
new PageCacheRecycler(Settings.EMPTY), | ||
new NoopCircuitBreaker("bench"), | ||
new Ordinator64.IdSpace() | ||
); | ||
for (int i = 0; i < testLongs.length; i++) { | ||
targetInts[i] = hash.add(testLongs[i]); | ||
} | ||
if (hash.currentSize() != unique) { | ||
throw new AssertionError("expected " + hash.currentSize() + " to be " + unique); | ||
} | ||
} | ||
|
||
@Benchmark | ||
@OperationsPerInvocation(ITERATIONS) | ||
public void ordinatorArray() { | ||
Ordinator64 hash = new Ordinator64( | ||
new PageCacheRecycler(Settings.EMPTY), | ||
new NoopCircuitBreaker("bench"), | ||
new Ordinator64.IdSpace() | ||
); | ||
hash.add(testLongs, targetInts, testLongs.length); | ||
if (hash.currentSize() != unique) { | ||
throw new AssertionError(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,7 +123,7 @@ public static void configureCompile(Project project) { | |
// fail on all javac warnings. | ||
// TODO Discuss moving compileOptions.getCompilerArgs() to use provider api with Gradle team. | ||
List<String> compilerArgs = compileOptions.getCompilerArgs(); | ||
compilerArgs.add("-Werror"); | ||
// compilerArgs.add("-Werror"); NOCOMMIT add me back once we figure out how to not fail compiling with preview features | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This'd be a huge problem to commit, but I can't figure out a good way around it. If I enable the vector API it'll emit the warning. I think Lucene has some kind of hack for accessing the vector API that I think would fix this. And we'd want to steal that. |
||
compilerArgs.add("-Xlint:all,-path,-serial,-options,-deprecation,-try,-removal"); | ||
compilerArgs.add("-Xdoclint:all"); | ||
compilerArgs.add("-Xdoclint:-missing"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,10 @@ tasks.named("compileJava").configure { | |
options.compilerArgs.addAll(["-s", "${projectDir}/src/main/generated"]) | ||
} | ||
|
||
tasks.named('forbiddenApisMain').configure { | ||
failOnMissingClasses = false // Ignore the vector apis | ||
} | ||
|
||
tasks.named('checkstyleMain').configure { | ||
source = "src/main/java" | ||
excludes = [ "**/*.java.st" ] | ||
|
@@ -396,4 +400,20 @@ tasks.named('stringTemplates').configure { | |
it.inputFile = multivalueDedupeInputFile | ||
it.outputFile = "org/elasticsearch/compute/operator/MultivalueDedupeBytesRef.java" | ||
} | ||
File blockHashInputFile = new File("${projectDir}/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st") | ||
template { | ||
it.properties = intProperties | ||
it.inputFile = blockHashInputFile | ||
it.outputFile = "org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java" | ||
} | ||
template { | ||
it.properties = longProperties | ||
it.inputFile = blockHashInputFile | ||
it.outputFile = "org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java" | ||
} | ||
template { | ||
it.properties = doubleProperties | ||
it.inputFile = blockHashInputFile | ||
it.outputFile = "org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java" | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm generating these so it's easier to keep them updated. I'll generate some more |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated this to work with the new hash, but it doesn't produce the lovely performance numbers - yet. Partly that's because we're not integrating with the hash super well - the vector case needs to consume the array somehow. Or something similar. But that feels like something for another time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other reason this doesn't show the performance bump we expect is because we don't enable all of the other aggregations - and because we don't aggregate much larger groups. Either way, this benchmark is much better at showing the performance of aggs, not the groupings. At least not yet.