This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch xuanwo/java-get-perf
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit 712a7a69fe46fe52a6e872938f2ded231bda4d91
Author: Xuanwo <[email protected]>
AuthorDate: Mon Dec 22 21:20:00 2025 +0800

    Add benckmark for read
---
 .../opendal/bench/OperatorReadBenchmark.java       | 237 +++++++++++++++++++++
 1 file changed, 237 insertions(+)

diff --git 
a/bindings/java/src/test/java/org/apache/opendal/bench/OperatorReadBenchmark.java
 
b/bindings/java/src/test/java/org/apache/opendal/bench/OperatorReadBenchmark.java
new file mode 100644
index 000000000..59aca8826
--- /dev/null
+++ 
b/bindings/java/src/test/java/org/apache/opendal/bench/OperatorReadBenchmark.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.opendal.bench;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import org.apache.opendal.Operator;
+import org.apache.opendal.OperatorInputStream;
+
+/**
+ * A minimal Java-side benchmark to compare range read vs InputStream read.
+ *
+ * <p>This benchmark intentionally avoids adding new dependencies (e.g. JMH). 
It is designed for
+ * quick iteration and relative comparison.
+ *
+ * <p>Environment variables:
+ *
+ * <ul>
+ *   <li>{@code OPENDAL_BENCH_SCHEME}: service scheme, default {@code memory}
+ *   <li>{@code OPENDAL_BENCH_CONFIG}: config map, format {@code k1=v1,k2=v2}, 
default empty
+ *   <li>{@code OPENDAL_BENCH_SIZE_BYTES}: object size in bytes, default 
{@code 16777216} (16 MiB)
+ *   <li>{@code OPENDAL_BENCH_BUFFER_BYTES}: stream buffer size, default 
{@code 8192}
+ *   <li>{@code OPENDAL_BENCH_WARMUP}: warmup iterations, default {@code 3}
+ *   <li>{@code OPENDAL_BENCH_ITERS}: measured iterations, default {@code 10}
+ * </ul>
+ */
+public final class OperatorReadBenchmark {
+    private OperatorReadBenchmark() {}
+
+    public static void main(String[] args) throws Exception {
+        final String scheme = getenvOrDefault("OPENDAL_BENCH_SCHEME", 
"memory");
+        final Map<String, String> config =
+                parseConfigMap(getenvOrDefault("OPENDAL_BENCH_CONFIG", ""));
+
+        final int sizeBytes = 
parsePositiveInt(getenvOrDefault("OPENDAL_BENCH_SIZE_BYTES", "16777216"));
+        final int bufferBytes = 
parsePositiveInt(getenvOrDefault("OPENDAL_BENCH_BUFFER_BYTES", "8192"));
+        final int warmup = 
parseNonNegativeInt(getenvOrDefault("OPENDAL_BENCH_WARMUP", "3"));
+        final int iters = 
parsePositiveInt(getenvOrDefault("OPENDAL_BENCH_ITERS", "10"));
+
+        final String path = "bench.bin";
+
+        try (final Operator op = Operator.of(scheme, config)) {
+            final byte[] content = new byte[sizeBytes];
+            new Random(0).nextBytes(content);
+            op.write(path, content);
+
+            System.out.println("OpenDAL Java benchmark");
+            System.out.println("scheme=" + scheme + " configKeys=" + 
config.keySet());
+            System.out.println("sizeBytes=" + sizeBytes + " bufferBytes=" + 
bufferBytes);
+            System.out.println("warmup=" + warmup + " iters=" + iters);
+            System.out.println();
+
+            final NullOutputStream out = new NullOutputStream();
+            long blackhole = 0;
+
+            for (int i = 0; i < warmup; i++) {
+                blackhole ^= benchRangeRead(op, path, sizeBytes, out);
+                blackhole ^= benchInputStreamRead(op, path, bufferBytes, out);
+            }
+
+            final Result range = measure("readRange", iters, sizeBytes, () -> 
benchRangeRead(op, path, sizeBytes, out));
+            final Result stream = measure("createInputStream", iters, 
sizeBytes, () -> benchInputStreamRead(op, path, bufferBytes, out));
+
+            blackhole ^= range.blackhole;
+            blackhole ^= stream.blackhole;
+
+            printResult(range);
+            printResult(stream);
+
+            if (blackhole == 0x1234_5678_9ABC_DEF0L) {
+                System.out.println("blackhole=" + blackhole);
+            }
+        }
+    }
+
+    private static long benchRangeRead(Operator op, String path, int 
sizeBytes, OutputStream out) throws IOException {
+        final byte[] bytes = op.read(path, 0, sizeBytes);
+        if (bytes.length != sizeBytes) {
+            throw new IllegalStateException("short read: " + bytes.length + " 
!= " + sizeBytes);
+        }
+        out.write(bytes);
+        return ((long) bytes[0] << 32) ^ bytes[bytes.length - 1];
+    }
+
+    private static long benchInputStreamRead(Operator op, String path, int 
bufferBytes, OutputStream out)
+            throws IOException {
+        final byte[] buf = new byte[bufferBytes];
+        long total = 0;
+        long blackhole = 0;
+
+        try (final OperatorInputStream in = op.createInputStream(path)) {
+            while (true) {
+                final int n = in.read(buf);
+                if (n < 0) {
+                    break;
+                }
+                if (n > 0) {
+                    out.write(buf, 0, n);
+                    total += n;
+                    blackhole ^= ((long) buf[0] << 32) ^ buf[n - 1];
+                }
+            }
+        }
+
+        if (total == 0) {
+            return blackhole;
+        }
+        return blackhole ^ total;
+    }
+
+    private static Result measure(String name, int iters, int bytesPerIter, 
ThrowingLongSupplier body) throws Exception {
+        final long start = System.nanoTime();
+        long blackhole = 0;
+        for (int i = 0; i < iters; i++) {
+            blackhole ^= body.getAsLong();
+        }
+        final long end = System.nanoTime();
+        return new Result(name, iters, bytesPerIter, end - start, blackhole);
+    }
+
+    private static void printResult(Result r) {
+        final double seconds = r.elapsedNanos / 1_000_000_000.0;
+        final long totalBytes = (long) r.iters * (long) r.bytesPerIter;
+        final double mib = totalBytes / (1024.0 * 1024.0);
+        final double mibPerSec = mib / seconds;
+        final double nsPerByte = (double) r.elapsedNanos / (double) totalBytes;
+
+        System.out.println(
+                r.name
+                        + ": "
+                        + String.format("%.2f", mibPerSec)
+                        + " MiB/s"
+                        + "  "
+                        + String.format("%.2f", nsPerByte)
+                        + " ns/B"
+                        + "  "
+                        + "(elapsed="
+                        + String.format("%.3f", seconds)
+                        + "s)");
+    }
+
+    private static String getenvOrDefault(String key, String defaultValue) {
+        final String v = System.getenv(key);
+        if (v == null || v.trim().isEmpty()) {
+            return defaultValue;
+        }
+        return v.trim();
+    }
+
+    private static int parsePositiveInt(String s) {
+        final int v = Integer.parseInt(s);
+        if (v <= 0) {
+            throw new IllegalArgumentException("value must be positive: " + s);
+        }
+        return v;
+    }
+
+    private static int parseNonNegativeInt(String s) {
+        final int v = Integer.parseInt(s);
+        if (v < 0) {
+            throw new IllegalArgumentException("value must be non-negative: " 
+ s);
+        }
+        return v;
+    }
+
+    private static Map<String, String> parseConfigMap(String s) {
+        final Map<String, String> map = new HashMap<String, String>();
+        final String trimmed = s.trim();
+        if (trimmed.isEmpty()) {
+            return map;
+        }
+        final String[] pairs = trimmed.split(",");
+        for (int i = 0; i < pairs.length; i++) {
+            final String pair = pairs[i].trim();
+            if (pair.isEmpty()) {
+                continue;
+            }
+            final int eq = pair.indexOf('=');
+            if (eq <= 0) {
+                throw new IllegalArgumentException("invalid config pair: " + 
pair);
+            }
+            final String k = pair.substring(0, eq).trim();
+            final String v = pair.substring(eq + 1).trim();
+            map.put(k, v);
+        }
+        return map;
+    }
+
+    private interface ThrowingLongSupplier {
+        long getAsLong() throws Exception;
+    }
+
+    private static final class Result {
+        private final String name;
+        private final int iters;
+        private final int bytesPerIter;
+        private final long elapsedNanos;
+        private final long blackhole;
+
+        private Result(String name, int iters, int bytesPerIter, long 
elapsedNanos, long blackhole) {
+            this.name = name;
+            this.iters = iters;
+            this.bytesPerIter = bytesPerIter;
+            this.elapsedNanos = elapsedNanos;
+            this.blackhole = blackhole;
+        }
+    }
+
+    private static final class NullOutputStream extends OutputStream {
+        @Override
+        public void write(int b) {}
+
+        @Override
+        public void write(byte[] b, int off, int len) {}
+    }
+}
+

Reply via email to