Created
September 17, 2025 10:32
-
-
Save puzpuzpuz/2e6844d16e73399789ca8b205560d700 to your computer and use it in GitHub Desktop.
Microbenchmark for https://github.com/questdb/questdb/pull/6068
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* ___ _ ____ ____ | |
* / _ \ _ _ ___ ___| |_| _ \| __ ) | |
* | | | | | | |/ _ \/ __| __| | | | _ \ | |
* | |_| | |_| | __/\__ \ |_| |_| | |_) | | |
* \__\_\\__,_|\___||___/\__|____/|____/ | |
* | |
* Copyright (c) 2014-2019 Appsicle | |
* Copyright (c) 2019-2024 QuestDB | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
******************************************************************************/ | |
package org.questdb; | |
import io.questdb.std.Decimal256; | |
import io.questdb.std.NumericException; | |
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.Scope; | |
import org.openjdk.jmh.annotations.Setup; | |
import org.openjdk.jmh.annotations.State; | |
import org.openjdk.jmh.annotations.Warmup; | |
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.math.RoundingMode; | |
import java.util.concurrent.TimeUnit; | |
@State(Scope.Benchmark) | |
@BenchmarkMode(Mode.AverageTime) | |
@OutputTimeUnit(TimeUnit.MICROSECONDS) | |
@Warmup(iterations = 3, time = 1) | |
@Measurement(iterations = 5, time = 1) | |
@Fork(1) | |
public class Decimal256AccessBenchmark { | |
private static final int ROWS = 1_000_000; | |
private final MockRecord record = new MockRecord(); | |
private AlternativeSubFunction alternativeFunc; | |
// Test data representing column values and constants | |
private long[] colHH; | |
private long[] colHL; | |
private long[] colLH; | |
private long[] colLL; | |
private CurrentSubFunction currentFunc; | |
public static void main(String[] args) throws RunnerException { | |
Options opt = new OptionsBuilder() | |
.include(Decimal256AccessBenchmark.class.getSimpleName()) | |
.build(); | |
new Runner(opt).run(); | |
} | |
@Benchmark | |
public long benchmarkAlternativeApproach() { | |
long sum = 0; | |
for (int i = 0; i < ROWS; i++) { | |
record.setIndex(i); | |
Decimal256 result = alternativeFunc.getDecimal256(record); | |
sum += result.getHh(); | |
sum += result.getHl(); | |
sum += result.getLh(); | |
sum += result.getLl(); | |
} | |
return sum; | |
} | |
@Benchmark | |
public long benchmarkCurrentApproach() { | |
long sum = 0; | |
for (int i = 0; i < ROWS; i++) { | |
record.setIndex(i); | |
sum += currentFunc.getDecimal256HH(record); | |
sum += currentFunc.getDecimal256HL(record); | |
sum += currentFunc.getDecimal256LH(record); | |
sum += currentFunc.getDecimal256LL(record); | |
} | |
return sum; | |
} | |
@Setup | |
public void setup() { | |
// Initialize test data | |
colHH = new long[ROWS]; | |
colHL = new long[ROWS]; | |
colLH = new long[ROWS]; | |
colLL = new long[ROWS]; | |
for (int i = 0; i < ROWS; i++) { | |
// Generate some varied decimal values | |
colHH[i] = 0; | |
colHL[i] = 0; | |
colLH[i] = i + 1; | |
colLL[i] = (i + 1) * 1000000L; | |
} | |
// Initialize constants | |
long const1HH = 0; | |
long const1HL = 0; | |
long const1LH = 0; | |
long const1LL = 2000000L; // 2.0 | |
long const2HH = 0; | |
long const2HL = 0; | |
long const2LH = 0; | |
long const2LL = 5000000L; // 5.0 | |
long const3HH = 0; | |
long const3HL = 0; | |
long const3LH = 0; | |
long const3LL = 3000000L; // 3.0 | |
long const4HH = 0; | |
long const4HL = 0; | |
long const4LH = 0; | |
long const4LL = 1000000L; // 1.0 | |
// Initialize individual operation functions with proper constructors | |
AlternativeColumnFunction altCol = new AlternativeColumnFunction(); | |
AlternativeConstFunction altConst1 = new AlternativeConstFunction(const1HH, const1HL, const1LH, const1LL); | |
AlternativeConstFunction altConst2 = new AlternativeConstFunction(const2HH, const2HL, const2LH, const2LL); | |
AlternativeConstFunction altConst3 = new AlternativeConstFunction(const3HH, const3HL, const3LH, const3LL); | |
AlternativeConstFunction altConst4 = new AlternativeConstFunction(const4HH, const4HL, const4LH, const4LL); | |
CurrentColumnFunction currentCol = new CurrentColumnFunction(); | |
CurrentConstFunction currentConst1 = new CurrentConstFunction(const1HH, const1HL, const1LH, const1LL); | |
CurrentConstFunction currentConst2 = new CurrentConstFunction(const2HH, const2HL, const2LH, const2LL); | |
CurrentConstFunction currentConst3 = new CurrentConstFunction(const3HH, const3HL, const3LH, const3LL); | |
CurrentConstFunction currentConst4 = new CurrentConstFunction(const4HH, const4HL, const4LH, const4LL); | |
// Expression: (col * const1 / const2) % const3 + col - const4 | |
AlternativeFunction altMulFunc = new AlternativeMulFunction(altCol, altConst1); | |
AlternativeFunction altDivFunc = new AlternativeDivFunction(altMulFunc, altConst2); | |
AlternativeFunction altRemFunc = new AlternativeRemFunction(altDivFunc, altConst3); | |
AlternativeFunction altAddFunc = new AlternativeAddFunction(altRemFunc, altCol); | |
alternativeFunc = new AlternativeSubFunction(altAddFunc, altConst4); | |
CurrentFunction currentMulFunc = new CurrentMulFunction(currentCol, currentConst1); | |
CurrentFunction currentDivFunc = new CurrentDivFunction(currentMulFunc, currentConst2); | |
CurrentFunction currentRemFunc = new CurrentRemFunction(currentDivFunc, currentConst3); | |
CurrentFunction currentAddFunc = new CurrentAddFunction(currentRemFunc, currentCol); | |
currentFunc = new CurrentSubFunction(currentAddFunc, currentConst4); | |
} | |
// Interface for alternative approach using Decimal256 objects | |
interface AlternativeFunction { | |
Decimal256 getDecimal256(MockRecord record); | |
} | |
// Interface for current approach using individual long accessors | |
interface CurrentFunction { | |
long getDecimal256HH(MockRecord record); | |
long getDecimal256HL(MockRecord record); | |
long getDecimal256LH(MockRecord record); | |
long getDecimal256LL(MockRecord record); | |
} | |
private class AlternativeAddFunction implements AlternativeFunction { | |
private final AlternativeFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final AlternativeFunction right; | |
AlternativeAddFunction(AlternativeFunction left, AlternativeFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
try { | |
Decimal256 leftVal = left.getDecimal256(record); | |
Decimal256 rightVal = right.getDecimal256(record); | |
result.copyFrom(leftVal); | |
result.add(rightVal); | |
return result; | |
} catch (NumericException e) { | |
result.ofNull(); | |
return result; | |
} | |
} | |
} | |
private class AlternativeColumnFunction implements AlternativeFunction { | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
return record.getDecimal256(); | |
} | |
} | |
private class AlternativeConstFunction implements AlternativeFunction { | |
private final Decimal256 constant = new Decimal256(); | |
AlternativeConstFunction(long hh, long hl, long lh, long ll) { | |
constant.of(hh, hl, lh, ll, 6); | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
return constant; | |
} | |
} | |
private class AlternativeDivFunction implements AlternativeFunction { | |
private final AlternativeFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final AlternativeFunction right; | |
AlternativeDivFunction(AlternativeFunction left, AlternativeFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
try { | |
Decimal256 leftVal = left.getDecimal256(record); | |
Decimal256 rightVal = right.getDecimal256(record); | |
result.copyFrom(leftVal); | |
result.divide(rightVal, 6, RoundingMode.HALF_UP); | |
return result; | |
} catch (NumericException e) { | |
result.ofNull(); | |
return result; | |
} | |
} | |
} | |
private class AlternativeMulFunction implements AlternativeFunction { | |
private final AlternativeFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final AlternativeFunction right; | |
AlternativeMulFunction(AlternativeFunction left, AlternativeFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
try { | |
Decimal256 leftVal = left.getDecimal256(record); | |
Decimal256 rightVal = right.getDecimal256(record); | |
result.copyFrom(leftVal); | |
result.multiply(rightVal); | |
return result; | |
} catch (NumericException e) { | |
result.ofNull(); | |
return result; | |
} | |
} | |
} | |
private class AlternativeRemFunction implements AlternativeFunction { | |
private final AlternativeFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final AlternativeFunction right; | |
AlternativeRemFunction(AlternativeFunction left, AlternativeFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
try { | |
Decimal256 leftVal = left.getDecimal256(record); | |
Decimal256 rightVal = right.getDecimal256(record); | |
result.copyFrom(leftVal); | |
result.modulo(rightVal); | |
return result; | |
} catch (NumericException e) { | |
result.ofNull(); | |
return result; | |
} | |
} | |
} | |
private class AlternativeSubFunction implements AlternativeFunction { | |
private final AlternativeFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final AlternativeFunction right; | |
AlternativeSubFunction(AlternativeFunction left, AlternativeFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public Decimal256 getDecimal256(MockRecord record) { | |
try { | |
Decimal256 leftVal = left.getDecimal256(record); | |
Decimal256 rightVal = right.getDecimal256(record); | |
result.copyFrom(leftVal); | |
result.subtract(rightVal); | |
return result; | |
} catch (NumericException e) { | |
result.ofNull(); | |
return result; | |
} | |
} | |
} | |
private class CurrentAddFunction extends CurrentFunctionBase { | |
private final CurrentFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final CurrentFunction right; | |
CurrentAddFunction(CurrentFunction left, CurrentFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
calculateResult(record); | |
return result.getHh(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return result.getHl(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return result.getLh(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return result.getLl(); | |
} | |
private void calculateResult(MockRecord record) { | |
try { | |
long leftHH = left.getDecimal256HH(record); | |
long leftHL = left.getDecimal256HL(record); | |
long leftLH = left.getDecimal256LH(record); | |
long leftLL = left.getDecimal256LL(record); | |
long rightHH = right.getDecimal256HH(record); | |
long rightHL = right.getDecimal256HL(record); | |
long rightLH = right.getDecimal256LH(record); | |
long rightLL = right.getDecimal256LL(record); | |
result.of(leftHH, leftHL, leftLH, leftLL, 6); | |
result.add(rightHH, rightHL, rightLH, rightLL, 6); | |
} catch (NumericException e) { | |
result.ofNull(); | |
} | |
} | |
} | |
private class CurrentColumnFunction extends CurrentFunctionBase { | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
return record.getDecimal256HH(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return record.getDecimal256HL(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return record.getDecimal256LH(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return record.getDecimal256LL(); | |
} | |
} | |
private class CurrentConstFunction extends CurrentFunctionBase { | |
private final long constHH, constHL, constLH, constLL; | |
CurrentConstFunction(long hh, long hl, long lh, long ll) { | |
this.constHH = hh; | |
this.constHL = hl; | |
this.constLH = lh; | |
this.constLL = ll; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
return constHH; | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return constHL; | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return constLH; | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return constLL; | |
} | |
} | |
private class CurrentDivFunction extends CurrentFunctionBase { | |
private final CurrentFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final CurrentFunction right; | |
CurrentDivFunction(CurrentFunction left, CurrentFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
calculateResult(record); | |
return result.getHh(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return result.getHl(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return result.getLh(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return result.getLl(); | |
} | |
private void calculateResult(MockRecord record) { | |
try { | |
long leftHH = left.getDecimal256HH(record); | |
long leftHL = left.getDecimal256HL(record); | |
long leftLH = left.getDecimal256LH(record); | |
long leftLL = left.getDecimal256LL(record); | |
long rightHH = right.getDecimal256HH(record); | |
long rightHL = right.getDecimal256HL(record); | |
long rightLH = right.getDecimal256LH(record); | |
long rightLL = right.getDecimal256LL(record); | |
result.of(leftHH, leftHL, leftLH, leftLL, 6); | |
result.divide(rightHH, rightHL, rightLH, rightLL, 6, 6, RoundingMode.HALF_UP); | |
} catch (NumericException e) { | |
result.ofNull(); | |
} | |
} | |
} | |
// Current approach using individual long accessor methods | |
private abstract class CurrentFunctionBase implements CurrentFunction { | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
throw new UnsupportedOperationException("Not implemented in base class"); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
throw new UnsupportedOperationException("Not implemented in base class"); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
throw new UnsupportedOperationException("Not implemented in base class"); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
throw new UnsupportedOperationException("Not implemented in base class"); | |
} | |
} | |
private class CurrentMulFunction extends CurrentFunctionBase { | |
private final CurrentFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final CurrentFunction right; | |
CurrentMulFunction(CurrentFunction left, CurrentFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
calculateResult(record); | |
return result.getHh(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return result.getHl(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return result.getLh(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return result.getLl(); | |
} | |
private void calculateResult(MockRecord record) { | |
try { | |
long leftHH = left.getDecimal256HH(record); | |
long leftHL = left.getDecimal256HL(record); | |
long leftLH = left.getDecimal256LH(record); | |
long leftLL = left.getDecimal256LL(record); | |
long rightHH = right.getDecimal256HH(record); | |
long rightHL = right.getDecimal256HL(record); | |
long rightLH = right.getDecimal256LH(record); | |
long rightLL = right.getDecimal256LL(record); | |
result.of(leftHH, leftHL, leftLH, leftLL, 6); | |
result.multiply(rightHH, rightHL, rightLH, rightLL, 6); | |
} catch (NumericException e) { | |
result.ofNull(); | |
} | |
} | |
} | |
private class CurrentRemFunction extends CurrentFunctionBase { | |
private final CurrentFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final CurrentFunction right; | |
CurrentRemFunction(CurrentFunction left, CurrentFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
calculateResult(record); | |
return result.getHh(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return result.getHl(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return result.getLh(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return result.getLl(); | |
} | |
private void calculateResult(MockRecord record) { | |
try { | |
long leftHH = left.getDecimal256HH(record); | |
long leftHL = left.getDecimal256HL(record); | |
long leftLH = left.getDecimal256LH(record); | |
long leftLL = left.getDecimal256LL(record); | |
long rightHH = right.getDecimal256HH(record); | |
long rightHL = right.getDecimal256HL(record); | |
long rightLH = right.getDecimal256LH(record); | |
long rightLL = right.getDecimal256LL(record); | |
result.of(leftHH, leftHL, leftLH, leftLL, 6); | |
result.modulo(rightHH, rightHL, rightLH, rightLL, 6); | |
} catch (NumericException e) { | |
result.ofNull(); | |
} | |
} | |
} | |
private class CurrentSubFunction extends CurrentFunctionBase { | |
private final CurrentFunction left; | |
private final Decimal256 result = new Decimal256(); | |
private final CurrentFunction right; | |
CurrentSubFunction(CurrentFunction left, CurrentFunction right) { | |
this.left = left; | |
this.right = right; | |
} | |
@Override | |
public long getDecimal256HH(MockRecord record) { | |
calculateResult(record); | |
return result.getHh(); | |
} | |
@Override | |
public long getDecimal256HL(MockRecord record) { | |
return result.getHl(); | |
} | |
@Override | |
public long getDecimal256LH(MockRecord record) { | |
return result.getLh(); | |
} | |
@Override | |
public long getDecimal256LL(MockRecord record) { | |
return result.getLl(); | |
} | |
private void calculateResult(MockRecord record) { | |
try { | |
long leftHH = left.getDecimal256HH(record); | |
long leftHL = left.getDecimal256HL(record); | |
long leftLH = left.getDecimal256LH(record); | |
long leftLL = left.getDecimal256LL(record); | |
long rightHH = right.getDecimal256HH(record); | |
long rightHL = right.getDecimal256HL(record); | |
long rightLH = right.getDecimal256LH(record); | |
long rightLL = right.getDecimal256LL(record); | |
result.of(leftHH, leftHL, leftLH, leftLL, 6); | |
result.subtract(rightHH, rightHL, rightLH, rightLL, 6); | |
} catch (NumericException e) { | |
result.ofNull(); | |
} | |
} | |
} | |
// Mock record implementation | |
private class MockRecord { | |
private int index; | |
public void setIndex(int index) { | |
this.index = index; | |
} | |
Decimal256 getDecimal256() { | |
Decimal256 result = new Decimal256(); | |
result.of(colHH[index], colHL[index], colLH[index], colLL[index], 6); // scale = 6 | |
return result; | |
} | |
long getDecimal256HH() { | |
return colHH[index]; | |
} | |
long getDecimal256HL() { | |
return colHL[index]; | |
} | |
long getDecimal256LH() { | |
return colLH[index]; | |
} | |
long getDecimal256LL() { | |
return colLL[index]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment