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));