From 33e17483c2db9d92b51204a728d92a637a382578 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Fri, 7 Nov 2025 16:33:51 +0000 Subject: [PATCH] Add benchmarks for CursorIterable for all key range types --- report-cursoriterable.sh | 307 ++++++++++++++++++ run-cursoriterable.sh | 279 ++++++++++++++++ .../bench/LmdbJavaCursorIterable.java | 222 +++++++++++++ 3 files changed, 808 insertions(+) create mode 100755 report-cursoriterable.sh create mode 100755 run-cursoriterable.sh create mode 100644 src/main/java/org/lmdbjava/bench/LmdbJavaCursorIterable.java diff --git a/report-cursoriterable.sh b/report-cursoriterable.sh new file mode 100755 index 0000000..49affcf --- /dev/null +++ b/report-cursoriterable.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# +# Copyright © 2016-2025 The LmdbJava Open Source Project +# +# 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. +# + +set -euo pipefail + +# Pure HTML report generator for LmdbJava version regression testing + +# Source common functions +source "$(dirname "$0")/report-common.sh" + +DATA_DIR="target/benchmark-cursoriterable" +WORK_DIR="target/benchmark" + +# Check prerequisites +JSON_COUNT=$(find "$DATA_DIR" -name "out-*.json" 2>/dev/null | wc -l) +if [ $JSON_COUNT -lt 2 ]; then + echo "ERROR: Need at least 2 version benchmark result files in $DATA_DIR" + exit 1 +fi + +echo "Found ${JSON_COUNT} version benchmark results" + +# Get system info +CPU_MODEL=$(get_cpu_info) +CPU_COUNT=$(get_cpu_count) +RAM_GIB=$(get_total_ram_gib) +KERNEL=$(get_kernel) +JAVA_TAG=$(get_java_version) + +FIRST_FILE=$(find "$DATA_DIR" \( -name "out-*.json" \) 2>/dev/null | head -1) +BENCH_DATE=$(stat -c %y "$FIRST_FILE" | cut -d' ' -f1) +BENCH_MODE=$(get_benchmark_mode "$FIRST_FILE") + +mkdir -p "$WORK_DIR" +cp "$DATA_DIR"/out-*.json "$WORK_DIR/" 2>/dev/null || true + +cd "$WORK_DIR" + +# List all files +FILES=$(find . -name "*.json" -type f | sort) + +## Build version list +#VERSIONS=() +#for f in $FILES; do +# [ -f "$f" ] || continue +# VERSION=$(echo "$f" | sed 's/out-\(.*\)\-version-\(.*\)\.json/\2/' | sed 's/out-\(.*\)\-branch-\(.*\)\.json/branch-\2/') +# VERSIONS+=("$VERSION") +#done + +echo "Processing benchmarks and generating chart..." + +# Extract data and generate chart +for BENCH in all allBackward atLeast atLeastBackward atMost atMostBackward closed closedBackward closedOpen closedOpenBackward greaterThan greaterThanBackward lessThan lessThanBackward open openBackward openClosed openClosedBackward; do + > "vers-${BENCH}.dat" + for FILE in ${FILES}; do +# if [[ "$VERSION" == branch-* ]]; then +# FILE="out-${VERSION}.json" +# else +# FILE="out-version-${VERSION}.json" +# fi + [ -f "$FILE" ] || continue + VERSION=$(echo "$FILE" | sed 's/\(.*\)\-version-\(.*\)\.json/\2/' | sed 's/\(.*\)\-branch-\(.*\)\.json/branch-\2/') + SCORE=$(jq -r ".[] | select(.benchmark | contains(\"LmdbJavaCursorIterable.${BENCH}\")) | + select(.params.intKey == \"true\") | select(.params.sequential == \"true\") | + .primaryMetric.score" "$FILE" 2>/dev/null || echo "") + if [ -n "$SCORE" ]; then + if [[ "$VERSION" == branch-* ]]; then + BRANCH_PART=$(echo "$VERSION" | sed 's/branch-\([^#]*\)#.*/\1/' | cut -c1-6) + GIT_PART=$(echo "$VERSION" | sed 's/.*#\(.*\)/\1/') + VERSION_LABEL="${BRANCH_PART}#${GIT_PART}" + else + VERSION_LABEL="$VERSION" + fi + echo "\"${VERSION_LABEL}\" ${SCORE}" >> "vers-${BENCH}.dat" + fi + done +done + +cat > vers-multiplot.gnuplot <<'GNUPLOT' +set terminal svg size 1000,6000 noenhanced +set output 'version-comparison.svg' +set style fill solid 0.25 border +set boxwidth 0.5 +set grid y +set multiplot layout 9,2 title "LmdbJava Performance Regression Testing\nMilliseconds per Operation (Smaller is Better)" +set ylabel "ms / operation" +set xlabel "" +set xtics nomirror rotate by -270 + +set title "all" +plot 'vers-all.dat' using 2:xtic(1) with boxes lc rgb "#F44336" notitle +set title "allBackward" +plot 'vers-allBackward.dat' using 2:xtic(1) with boxes lc rgb "#E91E63" notitle +set title "atLeast" +plot 'vers-atLeast.dat' using 2:xtic(1) with boxes lc rgb "#9C27B0" notitle +set title "atLeastBackward" +plot 'vers-atLeastBackward.dat' using 2:xtic(1) with boxes lc rgb "#673AB7" notitle +set title "atMost" +plot 'vers-atMost.dat' using 2:xtic(1) with boxes lc rgb "#3F51B5" notitle +set title "atMostBackward" +plot 'vers-atMostBackward.dat' using 2:xtic(1) with boxes lc rgb "#2196F3" notitle +set title "closed" +plot 'vers-closed.dat' using 2:xtic(1) with boxes lc rgb "#03A9F4" notitle +set title "closedBackward" +plot 'vers-closedBackward.dat' using 2:xtic(1) with boxes lc rgb "#00BCD4" notitle +set title "closedOpen" +plot 'vers-closedOpen.dat' using 2:xtic(1) with boxes lc rgb "#009688" notitle +set title "closedOpenBackward" +plot 'vers-closedOpenBackward.dat' using 2:xtic(1) with boxes lc rgb "#4CAF50" notitle +set title "greaterThan" +plot 'vers-greaterThan.dat' using 2:xtic(1) with boxes lc rgb "#8BC34A" notitle +set title "greaterThanBackward" +plot 'vers-greaterThanBackward.dat' using 2:xtic(1) with boxes lc rgb "#CDDC39" notitle +set title "lessThan" +plot 'vers-lessThan.dat' using 2:xtic(1) with boxes lc rgb "#FFEB3B" notitle +set title "lessThanBackward" +plot 'vers-lessThanBackward.dat' using 2:xtic(1) with boxes lc rgb "#FFC107" notitle +set title "open" +plot 'vers-open.dat' using 2:xtic(1) with boxes lc rgb "#FF9800" notitle +set title "openBackward" +plot 'vers-openBackward.dat' using 2:xtic(1) with boxes lc rgb "#FF5722" notitle +set title "openClosed" +plot 'vers-openClosed.dat' using 2:xtic(1) with boxes lc rgb "#795548" notitle +set title "openClosedBackward" +plot 'vers-openClosedBackward.dat' using 2:xtic(1) with boxes lc rgb "#607D8B" notitle +unset multiplot +GNUPLOT + +gnuplot vers-multiplot.gnuplot +rm -f vers-multiplot.gnuplot vers-*.dat + +echo "Generating HTML report..." + +# Generate pure HTML report +emit_html_header "LmdbJava Performance Regression Testing" > index.html +echo "

LmdbJava Performance Regression Testing

" >> index.html +emit_smoketest_warning "$BENCH_MODE" >> index.html + +cat >> index.html < +EOHTML + +emit_img "version-comparison.svg" "LmdbJava Performance Regression Testing" >> index.html + +cat >> index.html <<'EOHTML' + + +

Performance Analysis

+

The following tables show each benchmark ranked by performance, with percentage difference from the fastest version. + Branch versions (e.g., master#65df2ee) are highlighted in bold.

+ +EOHTML + +# Generate performance tables +declare -A BENCH_NAMES +BENCH_NAMES[all]="all" +BENCH_NAMES[allBackward]="allBackward" +BENCH_NAMES[atLeast]="atLeast" +BENCH_NAMES[atLeastBackward]="atLeastBackward" +BENCH_NAMES[atMost]="atMost" +BENCH_NAMES[atMostBackward]="atMostBackward" +BENCH_NAMES[closed]="closed" +BENCH_NAMES[closedBackward]="closedBackward" +BENCH_NAMES[closedOpen]="closedOpen" +BENCH_NAMES[closedOpenBackward]="closedOpenBackward" +BENCH_NAMES[greaterThan]="greaterThan" +BENCH_NAMES[greaterThanBackward]="greaterThanBackward" +BENCH_NAMES[lessThan]="lessThan" +BENCH_NAMES[lessThanBackward]="lessThanBackward" +BENCH_NAMES[open]="open" +BENCH_NAMES[openBackward]="openBackward" +BENCH_NAMES[openClosed]="openClosed" +BENCH_NAMES[openClosedBackward]="openClosedBackward" + +for BENCH in all allBackward atLeast atLeastBackward atMost atMostBackward closed closedBackward closedOpen closedOpenBackward greaterThan greaterThanBackward lessThan lessThanBackward open openBackward openClosed openClosedBackward; do + echo "

${BENCH_NAMES[$BENCH]}

" >> index.html + echo " " >> index.html + echo " " >> index.html + echo " " >> index.html + + declare -a SCORES + for FILE in ${FILES}; do +# if [[ "$VERSION" == branch-* ]]; then +# FILE="out-${VERSION}.json" +# else +# FILE="out-version-${VERSION}.json" +# fi + [ -f "$FILE" ] || continue + SCORE=$(jq -r ".[] | select(.benchmark | contains(\"LmdbJavaCursorIterable.${BENCH}\")) | + select(.params.intKey == \"true\") | select(.params.sequential == \"true\") | + .primaryMetric.score" "$FILE" 2>/dev/null || echo "") + if [ -n "$SCORE" ]; then + SCORES+=("$SCORE:$VERSION") + fi + done + + IFS=$'\n' SORTED=($(sort -t: -k1 -n <<<"${SCORES[*]}")) + unset IFS + FASTEST_SCORE=$(echo "${SORTED[0]}" | cut -d: -f1) + + RANK=1 + for ENTRY in "${SORTED[@]}"; do + SCORE=$(echo "$ENTRY" | cut -d: -f1) + VERSION=$(echo "$ENTRY" | cut -d: -f2) + + # Format version display + if [[ "$VERSION" == branch-* ]]; then + BRANCH_PART=$(echo "$VERSION" | sed 's/branch-\([^#]*\)#.*/\1/' | cut -c1-6) + GIT_PART=$(echo "$VERSION" | sed 's/.*#\(.*\)/\1/') + VERSION_DISPLAY="${BRANCH_PART}#${GIT_PART}" + else + VERSION_DISPLAY="$VERSION" + fi + + if [ "$RANK" -eq 1 ]; then + if [[ "$VERSION" == branch-* ]]; then + DIFF="baseline" + else + DIFF="baseline" + fi + else + PERCENT=$(awk -v score="$SCORE" -v fastest="$FASTEST_SCORE" 'BEGIN {printf "%.1f", ((score - fastest) / fastest * 100)}') + if [[ "$VERSION" == branch-* ]]; then + DIFF="+${PERCENT}%" + else + DIFF="+${PERCENT}%" + fi + fi + + SCORE_FMT=$(printf "%.3f" "$SCORE") + echo " " >> index.html + ((RANK++)) + done + + echo " " >> index.html + echo "
RankVersionms/opvs Fastest
$RANK$VERSION_DISPLAY$SCORE_FMT$DIFF
" >> index.html + unset SCORES +done + +cat >> index.html <Tested Versions +
    +EOHTML + +for FILE in ${FILES}; do + VERSION=$(echo "$FILE" | sed 's/\(.*\)\-version-\(.*\)\.json/\2/' | sed 's/\(.*\)\-branch-\(.*\)\.json/branch-\2/') + if [[ "$VERSION" == branch-* ]]; then + BRANCH_NAME=$(echo "$VERSION" | sed 's/branch-\([^#]*\)#.*/\1/') + GIT_REV=$(echo "$VERSION" | sed 's/.*#\(.*\)/\1/') + echo "
  • $VERSION
  • " >> index.html + else + echo "
  • $VERSION
  • " >> index.html + fi +done + +cat >> index.html < + +

    Test Configuration

    +

    The benchmark was executed on ${BENCH_DATE} using + LmdbJava Benchmarks.

    + +

    All tests use the ByteBuffer implementation with the following configuration:

    + +EOHTML + +# Emit system environment without LmdbJava version (pass empty strings) +emit_system_environment "$CPU_MODEL" "$CPU_COUNT" "$RAM_GIB" "$KERNEL" "$JAVA_TAG" >> index.html + +cat >> index.html <<'EOHTML' + +

    Benchmark Configuration

    +
      +
    • Implementation: ByteBuffer only
    • +
    • Test Profile: Run 18 different cursor operations
    • +
    • Key Type: Sequential 32-bit integers
    • +
    • Value Type: Sequential 32-bit integers
    • +
    • Access Pattern: Sequential
    • +
    • Benchmarks: All 18 operations (all allBackward atLeast atLeastBackward atMost atMostBackward closed closedBackward closedOpen closedOpenBackward greaterThan greaterThanBackward lessThan lessThanBackward open openBackward openClosed openClosedBackward)
    • +
    +EOHTML + +emit_html_footer >> index.html + +echo "" +echo "Report generation complete!" +echo "Generated: $WORK_DIR/index.html and $WORK_DIR/version-comparison.svg" +echo "" +echo "Open $WORK_DIR/index.html in your browser to view the report." + +cd - > /dev/null diff --git a/run-cursoriterable.sh b/run-cursoriterable.sh new file mode 100755 index 0000000..128126e --- /dev/null +++ b/run-cursoriterable.sh @@ -0,0 +1,279 @@ +#!/bin/bash +# +# Copyright © 2016-2025 The LmdbJava Open Source Project +# +# 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. +# + +set -euo pipefail + +# Usage: ./run-cursoriterable.sh [smoketest|benchmark [ram_percent]] +# smoketest: Fixed 1K entries for quick verification +# benchmark: Auto-scale entries based on RAM (default 25%, capped at 1M entries) + +MODE="${1:-benchmark}" +RAM_PERCENT="${2:-25}" + +# Detect total RAM in GB +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + TOTAL_RAM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') + TOTAL_RAM_GB=$((TOTAL_RAM_KB / 1024 / 1024)) +elif [[ "$OSTYPE" == "darwin"* ]]; then + TOTAL_RAM_BYTES=$(sysctl -n hw.memsize) + TOTAL_RAM_GB=$((TOTAL_RAM_BYTES / 1024 / 1024 / 1024)) +else + echo "Unsupported OS. Defaulting to 8 GB RAM assumption." + TOTAL_RAM_GB=8 +fi + +echo "Detected RAM: ${TOTAL_RAM_GB} GB" + +# LmdbJava versions to test +MAVEN_VERSIONS=(0.9.1) +BRANCH_VERSIONS=(pre-gh-273 master gh-249-remove-unsignedkey) + +case $MODE in + smoketest) + # Fixed small dataset for verification (fast, no warmup) + ITER_OPTS="-wi 0 -i 1 -f 1" + R_OPTS="-r 3s" + NUM_ENTRIES=1000 + echo "Running in SMOKETEST mode (1K entries, fast verification)" + ;; + + benchmark) + # Production benchmark with full warmup and iterations + ITER_OPTS="-wi 3 -i 3 -f 3" + R_OPTS="-r 120s" + + # Calculate max RAM in bytes (RAM_PERCENT of total) + MAX_RAM_GB=$((TOTAL_RAM_GB * RAM_PERCENT / 100)) + MAX_RAM_BYTES=$((MAX_RAM_GB * 1024 * 1024 * 1024)) + + echo "Max RAM usage: ${MAX_RAM_GB} GB (${RAM_PERCENT}% of ${TOTAL_RAM_GB} GB)" + + # Maximum entry count cap + MAX_ENTRIES=1000000 + + # Calculate entries based on Run 4 config (100 byte values, 4 byte key = 104 byte entries) + NUM_ENTRIES=$((MAX_RAM_BYTES / 104)) + [ $NUM_ENTRIES -gt $MAX_ENTRIES ] && NUM_ENTRIES=$MAX_ENTRIES + + echo "Calculated entry count: ${NUM_ENTRIES}" + ;; + + *) + echo "Usage: $0 [smoketest|benchmark [ram_percent]]" + echo " smoketest: Fixed 1K entries for quick verification" + echo " benchmark [percent]: Auto-scale entries based on system RAM (default 25%, max 1M entries)" + echo "" + echo "Examples:" + echo " $0 smoketest # Fast verification with 1K entries" + echo " $0 benchmark # Use 25% of system RAM (max 1M entries)" + echo " $0 benchmark 50 # Use 50% of system RAM (max 1M entries)" + exit 1 + ;; +esac + +# Create output directory (do not delete - allows resumption from failures) +FINAL_OUTPUT_DIR="target/benchmark-cursoriterable" +mkdir -p "$FINAL_OUTPUT_DIR" + +# Create temporary directory for results (survives mvn clean) +TEMP_OUTPUT_DIR=$(mktemp -d) +echo "Using temporary directory: ${TEMP_OUTPUT_DIR}" + +# JVM flags for Java 9+ module system compatibility +JVM_OPTS="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --enable-native-access=ALL-UNNAMED" + +# Backup original pom.xml +cp pom.xml pom.xml.backup + +echo "" +echo "Testing ${#MAVEN_VERSIONS[@]} Maven versions and ${#BRANCH_VERSIONS[@]} branch versions..." +echo "" + +# Output counter variable to keep outputs ordered +OUTPUT_COUNTER=0 + +# Function to update lmdbjava.version in pom.xml +update_pom_version() { + local version=$1 + sed -i "s|.*|${version}|g" pom.xml +} + +# Function to run benchmark for Run 4 sequential integer config only +run_benchmark() { + local output_file=$1 + local version_label=$2 + + # Check if benchmark already completed + if [ -s "${output_file}" ]; then + return 0 + fi + + echo " Running benchmark: ${version_label}..." + + # Run all cursor iterable benchmarks + java $JVM_OPTS -jar target/benchmarks.jar -rf json $ITER_OPTS $R_OPTS -to 60m -tu ms \ + -p num=${NUM_ENTRIES} -p valSize=20 -p sequential=true \ + -rff "${output_file}" \ + LmdbJavaCursorIterable || true + + if [ -f "${output_file}" ]; then + echo " ✓ Completed: ${version_label}" + else + echo " ✗ Failed: ${version_label}" + echo " Output file expected at: ${output_file}" + exit 1 + fi +} + +# Test Maven versions +for VERSION in "${MAVEN_VERSIONS[@]}"; do + echo "Testing Maven version: ${VERSION}" + + # Restore original pom.xml + cp pom.xml.backup pom.xml + + # Update version in pom.xml + update_pom_version "${VERSION}" + + # Build with new version + echo " Building with LmdbJava ${VERSION}..." + if ! mvn clean package -DskipTests -q; then + echo " ✗ Build failed for version ${VERSION}" + echo " ERROR: This version may not be compatible with current Java/benchmark code" + echo " Cannot continue with remaining tests" + exit 1 + fi + + # Run benchmark (temp dir survives mvn clean) + # Increment the output counter + OUTPUT_COUNTER=$((OUTPUT_COUNTER + 1)) + filename=$(printf "out-%03d-version-%s.json" "$OUTPUT_COUNTER" "$VERSION") + run_benchmark "${TEMP_OUTPUT_DIR}/${filename}" "version-${VERSION}" + + echo "" +done + +# Test branch versions +for BRANCH in "${BRANCH_VERSIONS[@]}"; do + echo "Testing branch: ${BRANCH}" + + # Restore original pom.xml + cp pom.xml.backup pom.xml + + # Clone branch into temporary directory + BRANCH_DIR="target/lmdbjava-src-${BRANCH}" + rm -rf "${BRANCH_DIR}" + + echo " Cloning branch ${BRANCH}..." + if ! git clone --depth 1 --branch "${BRANCH}" https://github.com/lmdbjava/lmdbjava.git "${BRANCH_DIR}" 2>&1 | grep -E "(Cloning|branch)"; then + echo " ✗ Failed to clone branch ${BRANCH}" + echo " ERROR: Cannot clone branch ${BRANCH} from GitHub" + echo " Cannot continue with remaining tests" + exit 1 + fi + + # Build and install the branch version + echo " Building branch ${BRANCH}..." + cd "${BRANCH_DIR}" + + # Extract version and git revision from branch's pom.xml and git log + BRANCH_VERSION=$(grep -m 1 "" pom.xml | sed 's/.*\(.*\)<\/version>.*/\1/') + GIT_REVISION=$(git rev-parse --short HEAD) + echo " Branch version: ${BRANCH_VERSION}" + echo " Git revision: ${GIT_REVISION}" + + if ! mvn clean install -DskipTests -Dfmt.skip -q; then + cd - > /dev/null + echo " ✗ Branch build failed for ${BRANCH}" + echo " ERROR: Cannot build branch ${BRANCH}" + echo " Cannot continue with remaining tests" + exit 1 + fi + + cd - > /dev/null + + # Update benchmark pom.xml to use branch version + update_pom_version "${BRANCH_VERSION}" + + # Rebuild benchmark with branch version + echo " Building benchmarks with branch ${BRANCH}..." + if ! mvn clean package -DskipTests -q; then + echo " ✗ Benchmark build failed for branch ${BRANCH}" + echo " ERROR: Cannot build benchmarks with branch ${BRANCH}" + echo " Cannot continue with remaining tests" + exit 1 + fi + + # Run benchmark (temp dir survives mvn clean) + # Include git revision in filename for debugging + # Increment the output counter + OUTPUT_COUNTER=$((OUTPUT_COUNTER + 1)) + filename=$(printf "out-%03d-branch-%s.json" "$OUTPUT_COUNTER" "$BRANCH") + run_benchmark "${TEMP_OUTPUT_DIR}/${filename}" "branch-${BRANCH}#${GIT_REVISION}" + + # Cleanup branch directory + rm -rf "${BRANCH_DIR}" + + echo "" +done + +# Restore original pom.xml +cp pom.xml.backup pom.xml +rm pom.xml.backup + +# Copy results from temp directory to final location +echo "" +echo "Copying results to ${FINAL_OUTPUT_DIR}..." +mkdir -p "${FINAL_OUTPUT_DIR}" +cp "${TEMP_OUTPUT_DIR}"/out-*.json "${FINAL_OUTPUT_DIR}/" 2>/dev/null || true + +# Cleanup temp directory +rm -rf "${TEMP_OUTPUT_DIR}" + +echo "" +echo "Version regression testing completed in $MODE mode" +if [ "$MODE" = "benchmark" ]; then + echo "RAM usage limit: ${RAM_PERCENT}% of ${TOTAL_RAM_GB} GB (max ${NUM_ENTRIES} entries)" +fi + +# Count successful results +EXPECTED_COUNT=$((${#MAVEN_VERSIONS[@]} + ${#BRANCH_VERSIONS[@]})) +ACTUAL_COUNT=$(find "${FINAL_OUTPUT_DIR}" -name "out-*.json" 2>/dev/null | wc -l) + +echo "" +echo "Results: ${ACTUAL_COUNT} of ${EXPECTED_COUNT} tests completed successfully" +if [ $ACTUAL_COUNT -lt $EXPECTED_COUNT ]; then + echo " WARNING: Some tests failed. Expected ${EXPECTED_COUNT} results, got ${ACTUAL_COUNT}" + exit 1 +fi + +echo "" +echo "Results available in:" +for file in "$FINAL_OUTPUT_DIR"/*; do + echo "$file" # Shows: ./subdirectory/file1.txt +done +#for VERSION in "${MAVEN_VERSIONS[@]}"; do +# [ -f "${FINAL_OUTPUT_DIR}/out-version-${VERSION}.json" ] && echo " - ${FINAL_OUTPUT_DIR}/out-version-${VERSION}.json" +#done +#for BRANCH in "${BRANCH_VERSIONS[@]}"; do +# # Use wildcard to match any git revision +# for f in "${FINAL_OUTPUT_DIR}"/out-branch-${BRANCH}#*.json; do +# [ -f "$f" ] && echo " - $f" +# done +#done +echo "" +echo "To generate a regression report from these results, run: ./report-cursoriterable.sh" diff --git a/src/main/java/org/lmdbjava/bench/LmdbJavaCursorIterable.java b/src/main/java/org/lmdbjava/bench/LmdbJavaCursorIterable.java new file mode 100644 index 0000000..b8a3c8f --- /dev/null +++ b/src/main/java/org/lmdbjava/bench/LmdbJavaCursorIterable.java @@ -0,0 +1,222 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * 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.lmdbjava.bench; + +import org.lmdbjava.CursorIterable; +import org.lmdbjava.CursorIterable.KeyVal; +import org.lmdbjava.KeyRange; +import org.lmdbjava.Txn; +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.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; +import org.openjdk.jmh.infra.Blackhole; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static java.nio.ByteBuffer.allocateDirect; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; +import static org.lmdbjava.ByteBufferProxy.PROXY_SAFE; +import static org.openjdk.jmh.annotations.Level.Trial; +import static org.openjdk.jmh.annotations.Mode.SampleTime; +import static org.openjdk.jmh.annotations.Scope.Benchmark; + +@OutputTimeUnit(MILLISECONDS) +@Fork(value = 1, jvmArgsAppend = "-Dlmdbjava.disable.checks=true") +@Warmup(iterations = 3) +@Measurement(iterations = 3) +@BenchmarkMode(SampleTime) + +public class LmdbJavaCursorIterable { + + @Benchmark + public void all(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.all()); + } + + @Benchmark + public void allBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.allBackward()); + } + + @Benchmark + public void atLeast(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.atLeast(r.minKey)); + } + + @Benchmark + public void atLeastBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.atLeastBackward(r.maxKey)); + } + + @Benchmark + public void atMost(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.atMost(r.maxKey)); + } + + @Benchmark + public void atMostBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.atMostBackward(r.minKey)); + } + + @Benchmark + public void closed(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.closed(r.minKey, r.maxKey)); + } + + @Benchmark + public void closedBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.closedBackward(r.maxKey, r.minKey)); + } + + @Benchmark + public void closedOpen(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.closedOpen(r.minKey, r.maxKey)); + } + + @Benchmark + public void closedOpenBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.closedOpenBackward(r.maxKey, r.minKey)); + } + + @Benchmark + public void greaterThan(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.greaterThan(r.minKey)); + } + + @Benchmark + public void greaterThanBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.greaterThanBackward(r.maxKey)); + } + + @Benchmark + public void lessThan(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.lessThan(r.maxKey)); + } + + @Benchmark + public void lessThanBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.lessThanBackward(r.minKey)); + } + + @Benchmark + public void open(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.open(r.minKey, r.maxKey)); + } + + @Benchmark + public void openBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.openBackward(r.maxKey, r.minKey)); + } + + @Benchmark + public void openClosed(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.openClosed(r.minKey, r.maxKey)); + } + + @Benchmark + public void openClosedBackward(final Reader r, final Blackhole bh) { + test(r, bh, KeyRange.openClosedBackward(r.maxKey, r.minKey)); + } + + private void test(final Reader r, final Blackhole bh, final KeyRange keyRange) { + try (final CursorIterable cursorIterable = r.db.iterate(r.txn, keyRange)) { + for (final KeyVal kv : cursorIterable) { + bh.consume(kv.key()); + bh.consume(kv.val()); + } + } + } + + @State(Benchmark) + public static class LmdbJava extends CommonLmdbJava { + + ByteBuffer rwKey; + ByteBuffer rwVal; + + @Override + public void setup(final BenchmarkParams b, final boolean sync) throws + IOException { + super.setup(b, sync); + rwKey = allocateDirect(Integer.BYTES * 2); + rwVal = allocateDirect(Long.BYTES); + } + + void write() { + try (Txn tx = env.txnWrite()) { + for (int i = 1; i <= num; i++) { + rwKey.putInt(i); + rwKey.flip(); + rwVal.putInt(i); + rwVal.flip(); + db.put(tx, rwKey, rwVal); + } + tx.commit(); + } + } + } + + @State(Benchmark) + public static class Reader extends LmdbJava { + + /** + * Whether the byte buffer accessor is safe or not. + */ + @Param("false") + boolean forceSafe; + Txn txn; + + ByteBuffer minKey; + ByteBuffer maxKey; + + @Setup(Trial) + @Override + public void setup(final BenchmarkParams b) throws IOException { + bufferProxy = forceSafe + ? PROXY_SAFE + : PROXY_OPTIMAL; + super.setup(b, false); + + minKey = ByteBuffer.allocateDirect(Integer.BYTES); + minKey.putInt(1); + minKey.flip(); + maxKey = ByteBuffer.allocateDirect(Integer.BYTES); + maxKey.putInt(num); + maxKey.flip(); + + super.write(); + txn = env.txnRead(); + + } + + @TearDown(Trial) + @Override + public void teardown() throws IOException { + txn.abort(); + super.teardown(); + } + } +}