Skip to content
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
43dd073
Initial experiments commit
JonasKunz Jul 8, 2025
da159a9
Clean up generator
JonasKunz Jul 8, 2025
fa4efe0
Clean up merger
JonasKunz Jul 8, 2025
fba967f
More tests, a bit of cleanup
JonasKunz Jul 9, 2025
cef3b11
Stash benchmark changes
JonasKunz Jul 9, 2025
aac9f6d
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 10, 2025
6a2b62f
spotless, checkstyle
JonasKunz Jul 10, 2025
eb955cd
more build fixes
JonasKunz Jul 10, 2025
2eb5fdd
Fix license headers
JonasKunz Jul 10, 2025
fd7064e
spotless round 2
JonasKunz Jul 10, 2025
66b5e2c
Fix tests, implement benchmarks
JonasKunz Jul 10, 2025
2f293d0
Reduce max scale to preserve numeric accuracy
JonasKunz Jul 10, 2025
91193bc
Check for sane scale and indices
JonasKunz Jul 10, 2025
92efdcf
Fix and clean percentile computation
JonasKunz Jul 11, 2025
e6924e9
Add some tests based on TDigestTest
JonasKunz Jul 11, 2025
cab3fdf
Clean up, bug fixes and javadoc
JonasKunz Jul 14, 2025
454a9cc
Remove dead code
JonasKunz Jul 14, 2025
486a8bd
A bit more javadoc
JonasKunz Jul 14, 2025
7c6655b
AI-assisted javadoc and spotless
JonasKunz Jul 15, 2025
a980c0e
Readme bullet points
JonasKunz Jul 15, 2025
a46914a
Add readme
JonasKunz Jul 15, 2025
fefd39b
Add testcase verifying index limits are not exceeded on upscaling
JonasKunz Jul 15, 2025
ac804c7
Replaced upscaling floating point arithmetic with faster and more acc…
JonasKunz Jul 15, 2025
5e1ca08
Readme fixes and clarifications
JonasKunz Jul 17, 2025
c091758
Review fixes
JonasKunz Jul 17, 2025
25be13d
Refactor bucket representation
JonasKunz Jul 17, 2025
6100bc6
Add test case for quantile in zero-bucket
JonasKunz Jul 17, 2025
7c99c81
Add more perecentiles for testing
JonasKunz Jul 17, 2025
b308838
Improved quantile algorithm to only iterate once over the buckets
JonasKunz Jul 17, 2025
311f44b
Fix quantile computation and error bound in tests
JonasKunz Jul 18, 2025
54fd41d
Update randomization in remaining tests
JonasKunz Jul 18, 2025
7dad089
Fix javadoc and benchmarks
JonasKunz Jul 18, 2025
522a72a
Checkstyle
JonasKunz Jul 18, 2025
33b626b
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 18, 2025
28ccef1
Remove DoubleStream usage
JonasKunz Jul 22, 2025
fe311d8
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 22, 2025
89cfb02
Update todos
JonasKunz Jul 22, 2025
39257b8
[CI] Auto commit changes from spotless
Jul 22, 2025
57fd335
Review fixes
JonasKunz Jul 24, 2025
cc13e0f
Merge remote-tracking branch 'origin/exponentional-histos' into expon…
JonasKunz Jul 24, 2025
2513ca2
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 24, 2025
9f8fa45
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 25, 2025
dbb3edd
Moved static creation methods into ExponentialHistogram interface
JonasKunz Jul 28, 2025
5668fc2
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 28, 2025
c63ba90
Replace exceptions for internal invariants with asserts
JonasKunz Jul 28, 2025
af51b7c
Cleanup benchmarks
JonasKunz Jul 29, 2025
2cea9a7
Felix review fixes
JonasKunz Jul 29, 2025
941223a
Replace more internal exceptions with assertions
JonasKunz Jul 29, 2025
0d2a50c
Review fixes
JonasKunz Jul 29, 2025
ff9467a
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 29, 2025
d34da19
spotless
JonasKunz Jul 29, 2025
b822e68
Replace readme with package-info
JonasKunz Jul 29, 2025
c44b0b5
Clarify index and slot in FixedCapacityExponentialHistogram
JonasKunz Jul 29, 2025
8f0456d
Change license to apache 2.0, add notice for opentelemetry code
JonasKunz Jul 30, 2025
d6fc366
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Jul 31, 2025
f170dcd
Merge remote-tracking branch 'elastic/main' into exponentional-histos
JonasKunz Aug 6, 2025
e19e944
Update license header with recommendation from legal
JonasKunz Aug 7, 2025
fdc257d
Merge branch 'main' into exponentional-histos
JonasKunz Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmarks/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
api(project(':x-pack:plugin:esql:compute'))
implementation project(path: ':libs:native')
implementation project(path: ':libs:simdvec')
implementation project(path: ':libs:exponential-histogram')
expression(project(path: ':modules:lang-expression', configuration: 'zip'))
painless(project(path: ':modules:lang-painless', configuration: 'zip'))
nativeLib(project(':libs:native'))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.benchmark.exponentialhistogram;

import org.elasticsearch.exponentialhistogram.ExponentialHistogramGenerator;
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.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.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.profile.StackProfiler;
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;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.DoubleSupplier;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
@State(Scope.Thread)
public class ExponentialHistogramGenerationBench {

@Param({ "100", "500", "1000", "5000", "10000", "20000" })
int bucketCount;

@Param({ "NORMAL", "GAUSSIAN" })
String distribution;

Random random;
ExponentialHistogramGenerator histoGenerator;

double[] data = new double[1000000];

int index;

@Setup
public void setUp() {
random = ThreadLocalRandom.current();
histoGenerator = new ExponentialHistogramGenerator(bucketCount);

DoubleSupplier nextRandom = () -> distribution.equals("GAUSSIAN") ? random.nextGaussian() : random.nextDouble();

// Make sure that we start with a non-empty histogram, as this distorts initial additions
for (int i = 0; i < 10000; ++i) {
histoGenerator.add(nextRandom.getAsDouble());
}

for (int i = 0; i < data.length; ++i) {
data[i] = nextRandom.getAsDouble();
}

index = 0;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void add() {
if (index >= data.length) {
index = 0;
}
histoGenerator.add(data[index++]);
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(".*" + ExponentialHistogramGenerationBench.class.getSimpleName() + ".*")
.warmupIterations(5)
.measurementIterations(5)
.addProfiler(GCProfiler.class)
.addProfiler(StackProfiler.class)
.build();

new Runner(opt).run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.benchmark.exponentialhistogram;

import org.elasticsearch.exponentialhistogram.BucketIterator;
import org.elasticsearch.exponentialhistogram.ExponentialHistogram;
import org.elasticsearch.exponentialhistogram.ExponentialHistogramGenerator;
import org.elasticsearch.exponentialhistogram.ExponentialHistogramMerger;
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.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.Threads;
import org.openjdk.jmh.annotations.Warmup;

import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
@State(Scope.Thread)
public class ExponentialHistogramMergeBench {

@Param({ "1000", "2000", "5000" })
int bucketCount;

@Param({ "0.01", "0.1", "0.25", "0.5", "1.0", "2.0" })
double mergedHistoSizeFactor;

Random random;
ExponentialHistogramMerger histoMerger;

ExponentialHistogram[] toMerge = new ExponentialHistogram[10_000];

int index;

@Setup
public void setUp() {
random = ThreadLocalRandom.current();
histoMerger = new ExponentialHistogramMerger(bucketCount);

ExponentialHistogramGenerator initial = new ExponentialHistogramGenerator(bucketCount);
for (int j = 0; j < bucketCount; j++) {
initial.add(Math.pow(1.001, j));
}
ExponentialHistogram initialHisto = initial.get();
int cnt = getBucketCount(initialHisto);
if (cnt < bucketCount) {
throw new IllegalArgumentException("Expected bucket count to be " + bucketCount + ", but was " + cnt);
}
histoMerger.add(initialHisto);

int dataPointSize = (int) Math.round(bucketCount * mergedHistoSizeFactor);

for (int i = 0; i < toMerge.length; i++) {
ExponentialHistogramGenerator generator = new ExponentialHistogramGenerator(dataPointSize);

int bucketIndex = 0;
for (int j = 0; j < dataPointSize; j++) {
bucketIndex += 1 + random.nextInt(bucketCount) % (Math.max(1, bucketCount / dataPointSize));
generator.add(Math.pow(1.001, bucketIndex));
}
toMerge[i] = generator.get();
cnt = getBucketCount(toMerge[i]);
if (cnt < dataPointSize) {
throw new IllegalArgumentException("Expected bucket count to be " + dataPointSize + ", but was " + cnt);
}
}

index = 0;
}

private static int getBucketCount(ExponentialHistogram histo) {
int cnt = 0;
for (BucketIterator it : List.of(histo.negativeBuckets().iterator(), histo.positiveBuckets().iterator())) {
while (it.hasNext()) {
cnt++;
it.advance();
}
}
return cnt;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void add() {
if (index >= toMerge.length) {
index = 0;
}
histoMerger.add(toMerge[index++]);
}
}
5 changes: 5 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
<sha256 value="3366d2c88fb576e486d830f521184e8f1839f8c15dcd2151a3f6e1f62b0b37a0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="ch.obermuhlner" name="big-math" version="2.3.2">
<artifact name="big-math-2.3.2.jar">
<sha256 value="693e1bb7c7f5184b448f03c2a2c0c45d07d8e89e4641fdc31ab0a8057027f43d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="ch.randelshofer" name="fastdoubleparser" version="0.8.0">
<artifact name="fastdoubleparser-0.8.0.jar">
<sha256 value="10fe288fd7a2cdaf5175332b73529f9abf8fd54dcfff317d6967c0c35ffb133b" origin="Generated by Gradle"/>
Expand Down
Loading