diff --git a/rskj-core/build.gradle b/rskj-core/build.gradle
index 8deb7a17fdc..9c5b0c8975c 100644
--- a/rskj-core/build.gradle
+++ b/rskj-core/build.gradle
@@ -194,6 +194,7 @@ ext {
// WARN: consider different setups and sure to not use GPG elements without checksums or without signature file
// in the remote repository (see https://github.com/gradle/gradle/security/advisories/GHSA-j6wc-xfg8-jx2j)
dependencies {
+ jmhImplementation project(path: ':rskj-core')
jmhImplementation "${jmhLibs.jmhCore}"
jmhImplementation "${jmhLibs.web3jCore}"
jmhImplementation "${libs.slf4jApiLib}"
diff --git a/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java b/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java
new file mode 100644
index 00000000000..755f3e6fb7a
--- /dev/null
+++ b/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java
@@ -0,0 +1,62 @@
+/*
+ * This file is part of RskJ
+ * Copyright (C) 2024 RSK Labs Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package co.rsk.jmh.utils;
+
+import co.rsk.core.types.bytes.Bytes;
+import co.rsk.jmh.utils.plan.DataPlan;
+import org.openjdk.jmh.annotations.*;
+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.concurrent.TimeUnit;
+
+@BenchmarkMode({Mode.Throughput})
+@Warmup(iterations = 1, time = 5 /* secs */)
+@Measurement(iterations = 3, time = 5 /* secs */)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+public class BenchmarkByteUtil {
+
+ @Benchmark
+ public void toHexString_Bouncycastle(DataPlan plan) {
+ byte[] data = plan.getData();
+ int off = plan.getNextRand(data.length);
+ int len = plan.getNextRand(data.length - off);
+ org.bouncycastle.util.encoders.Hex.toHexString(data, off, len);
+ }
+
+ @Benchmark
+ public void toHexString_V2(DataPlan plan) {
+ Bytes bytes = plan.getBytes();
+ int bytesLen = bytes.length();
+ int off = plan.getNextRand(bytesLen);
+ int len = plan.getNextRand(bytesLen - off);
+ bytes.toHexString(off, len);
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(BenchmarkByteUtil.class.getName())
+ .forks(2)
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java b/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java
new file mode 100644
index 00000000000..a9c5993f74e
--- /dev/null
+++ b/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of RskJ
+ * Copyright (C) 2024 RSK Labs Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package co.rsk.jmh.utils.plan;
+
+import co.rsk.core.types.bytes.Bytes;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+import java.util.Random;
+
+@State(Scope.Benchmark)
+public class DataPlan {
+
+ private final byte[] data = new byte[1024];
+
+ private Bytes bytes;
+
+ private Random random;
+
+ @Setup(Level.Trial)
+ public void doSetup() {
+ random = new Random(111);
+ random.nextBytes(data);
+ bytes = Bytes.of(data);
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public Bytes getBytes() {
+ return bytes;
+ }
+
+ public int getNextRand(int bound) {
+ return random.nextInt(bound);
+ }
+}
diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java
index d472f9f9240..aa7e179ce6e 100644
--- a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java
+++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java
@@ -18,6 +18,8 @@
package co.rsk.core.types.bytes;
+import org.ethereum.util.ByteUtil;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
@@ -70,13 +72,27 @@ default String toHexString() {
return toHexString(0, length());
}
- default String toHexStringV2(int off, int length) {
- if (off < 0 || length < 0 || off + length > length()) {
- throw new IndexOutOfBoundsException("invalid 'off' and/or 'length': " + off + "; " + length);
+ /**
+ * This is a bit optimized version of {@link ByteUtil#toHexString(byte[], int, int)},
+ * which does not use a third-party library.
+ *
+ * @param offset the start index of the bytes to be converted to hexadecimal.
+ * It must be non-negative and less than the length of the bytes.
+ * Otherwise, an {@link IndexOutOfBoundsException} will be thrown.
+ * @param length the number of bytes to be converted to hexadecimal.
+ * It must be non-negative and less than the length of the bytes.
+ * Otherwise, an {@link IndexOutOfBoundsException} will be thrown.
+ *
+ * @return the hexadecimal representation of the bytes in the range of {@code offset} and {@code length}.
+ */
+ default String toHexStringV2(int offset, int length) {
+ int endIndex = offset + length;
+ if (offset < 0 || length < 0 || endIndex > length()) {
+ throw new IndexOutOfBoundsException("invalid 'offset' and/or 'length': " + offset + "; " + length);
}
StringBuilder sb = new StringBuilder(length * 2);
- for (int i = off; i < off + length; i++) {
+ for (int i = offset; i < endIndex; i++) {
byte b = byteAt(i);
sb.append(Character.forDigit((b >> 4) & 0xF, 16));
sb.append(Character.forDigit((b & 0xF), 16));