Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ plugins {
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
id 'com.github.ben-manes.versions' version '0.49.0'
id 'org.jreleaser' version '1.16.0'
id 'me.champeau.jmh' version '0.7.3' apply false
}

description = 'A set of libraries and other tools to aid development of blockchain and other decentralized software in Java and other JVM languages'
Expand Down Expand Up @@ -124,6 +125,39 @@ subprojects {
}
}

if (file('src/jmh').directory) {
apply plugin: 'me.champeau.jmh'
tasks.named("jmh") {
description = "Usage: gradle jmh -Pincludes=MyBench -PasyncProfiler=<libPath> -PasyncProfilerOptions=<options>\n" +
"\nRun JMH benchmarks in each of the projects. Allows for controlling JMH execution directly from the command line.\n" +
"\t-Pincludes=<includePattern>\tInclude pattern (regular expression) for benchmarks to be executed. Defaults to including all benchmarks.\n" +
"\t-PasyncProfiler=<libPath>\tLibrary path to fetch the Async profiler from. Default is to disable profiling.\n" +
"\t-PasyncProfilerOptions=<asyncProfilerOptions>\tOptions to pass on to the Async profiler separated by ';'. Default is to produce a flamegraph with all other default profiler options.\n"
}

// to pass compilation as the compiler doesn't like what jmh tool is doing
compileJmhJava {
options.compilerArgs << '-Xlint:none'
}

jmh {
jmhVersion = '1.37'
fork = 3
includes = _strListCmdArg('includes', [''])
var asyncProfiler = _strCmdArg('asyncProfiler')
var asyncProfilerOptions = _strCmdArg('asyncProfilerOptions', 'output=flamegraph')
if (asyncProfiler != null) {
profilers = ['async:libPath=' + asyncProfiler + ';' + asyncProfilerOptions]
}
duplicateClassesStrategy = DuplicatesStrategy.WARN
jvmArgs = ['-XX:+EnableDynamicAgentLoading']
}

dependencies {
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
}
}

plugins.withId('java', { _ ->
sourceSets {
integrationTest {
Expand Down Expand Up @@ -567,3 +601,18 @@ tasks.register('checkNotice') {
}
throw new GradleException('NOTICE file is not up-to-date')
}

def _strListCmdArg(name, defaultValue) {
if (!project.hasProperty(name))
return defaultValue

return ((String) project.property(name)).tokenize(',')
}

def _strCmdArg(name) {
return _strCmdArg(name, null)
}

def _strCmdArg(name, defaultValue) {
return project.hasProperty(name) ? project.property(name) as String : defaultValue
}
1 change: 1 addition & 0 deletions bytes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation 'org.mockito:mockito-junit-jupiter'
testImplementation 'org.assertj:assertj-core'

testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
62 changes: 62 additions & 0 deletions bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright The Tuweni Authors
// SPDX-License-Identifier: Apache-2.0
package org.benchmark;

import org.apache.tuweni.bytes.Bytes;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(value = Mode.AverageTime)
@State(Scope.Thread)
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
public class BytesMegamorphicBenchmarkV1 {
private static final int N = 4;
private static final int FACTOR = 1_000;
private static final Random RANDOM = new Random(23L);
Bytes[] bytesV1;

@Param({"mono", "mega"})
private String mode;

@Setup
public void setup() {
bytesV1 = new Bytes[N * FACTOR];
for (int i = 0; i < N * FACTOR; i += N) {
bytesV1[i] = Bytes.wrap(getBytes(32));
bytesV1[i + 1] = "mega".equals(mode) ? Bytes.wrap(getBytes(48)) : Bytes.wrap(getBytes(32));
bytesV1[i + 2] =
"mega".equals(mode) ? Bytes.repeat((byte) 0x09, 16) : Bytes.wrap(getBytes(32));
bytesV1[i + 3] =
"mega".equals(mode) ? Bytes.wrap(bytesV1[i], bytesV1[i + 1]) : Bytes.wrap(getBytes(32));
}
}

private static byte[] getBytes(final int size) {
byte[] b = new byte[size];
RANDOM.nextBytes(b);
return b;
}

@Benchmark
@OperationsPerInvocation(N * FACTOR)
public void test() {
for (Bytes b : bytesV1) {
b.slice(1);
}
}
}
62 changes: 62 additions & 0 deletions bytes/src/jmh/java/org/benchmark/BytesMegamorphicBenchmarkV2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright The Tuweni Authors
// SPDX-License-Identifier: Apache-2.0
package org.benchmark;

import org.apache.tuweni.v2.bytes.Bytes;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(value = Mode.AverageTime)
@State(Scope.Thread)
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
public class BytesMegamorphicBenchmarkV2 {
private static final int N = 4;
private static final int FACTOR = 1_000;
private static final Random RANDOM = new Random(23L);
Bytes[] bytesV2;

@Param({"mono", "mega"})
private String mode;

@Setup
public void setup() {
bytesV2 = new Bytes[N * FACTOR];
for (int i = 0; i < N * FACTOR; i += N) {
bytesV2[i] = Bytes.wrap(getBytes(32));
bytesV2[i + 1] = "mega".equals(mode) ? Bytes.wrap(getBytes(48)) : Bytes.wrap(getBytes(32));
bytesV2[i + 2] =
"mega".equals(mode) ? Bytes.repeat((byte) 0x09, 16) : Bytes.wrap(getBytes(32));
bytesV2[i + 3] =
"mega".equals(mode) ? Bytes.wrap(bytesV2[i], bytesV2[i + 1]) : Bytes.wrap(getBytes(32));
}
}

private static byte[] getBytes(final int size) {
byte[] b = new byte[size];
RANDOM.nextBytes(b);
return b;
}

@Benchmark
@OperationsPerInvocation(N * FACTOR)
public void test() {
for (Bytes b : bytesV2) {
b.slice(1);
}
}
}
143 changes: 143 additions & 0 deletions bytes/src/main/java/org/apache/tuweni/v2/bytes/ArrayWrappingBytes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright The Tuweni Authors
// SPDX-License-Identifier: Apache-2.0
package org.apache.tuweni.v2.bytes;

import static org.apache.tuweni.v2.bytes.Utils.checkArgument;
import static org.apache.tuweni.v2.bytes.Utils.checkElementIndex;
import static org.apache.tuweni.v2.bytes.Utils.checkLength;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Arrays;

import io.vertx.core.buffer.Buffer;

class ArrayWrappingBytes extends Bytes {

protected byte[] bytes;
protected final int offset;

ArrayWrappingBytes(byte[] bytes) {
this(bytes, 0, bytes.length);
}

ArrayWrappingBytes(byte[] bytes, int offset, int length) {
super(length);
this.bytes = bytes;
this.offset = offset;
}

@Override
public byte get(int i) {
// Check bounds because while the array access would throw, the error message would be confusing
// for the caller.
checkElementIndex(offset + i, bytes.length);
checkElementIndex(i, size());
return bytes[offset + i];
}

@Override
public Bytes slice(int i, int length) {
if (i == 0 && length == size()) {
return this;
}

if (length == 0) {
return EMPTY;
}

checkArgument(length > 0, "Invalid negative length");
if (bytes.length > 0) {
checkElementIndex(offset + i, bytes.length);
}
checkLength(bytes.length, offset + i, length);

return new ArrayWrappingBytes(bytes, offset + i, length);
}

@Override
public int commonPrefixLength(Bytes other) {
if (!(other instanceof ArrayWrappingBytes o)) {
return super.commonPrefixLength(other);
}
int i = 0;
while (i < size() && i < o.size() && bytes[offset + i] == o.bytes[o.offset + i]) {
i++;
}
return i;
}

@Override
public void update(MessageDigest digest) {
digest.update(bytes, offset, size());
}

@Override
public void appendTo(ByteBuffer byteBuffer) {
byteBuffer.put(bytes, offset, size());
}

@Override
public MutableBytes mutableCopy() {
return MutableBytes.fromArray(bytes, offset, size());
}

@Override
public void appendTo(Buffer buffer) {
buffer.appendBytes(bytes, offset, size());
}

@Override
public byte[] toArrayUnsafe() {
if (offset == 0 && size() == bytes.length) {
return bytes;
}
return Arrays.copyOfRange(bytes, offset, offset + size());
}

@Override
protected void and(byte[] bytesArray, int offset, int length) {
Utils.and(this.bytes, this.offset, bytesArray, offset, length);
}

@Override
protected void or(byte[] bytesArray, int offset, int length) {
Utils.or(this.bytes, this.offset, bytesArray, offset, length);
}

@Override
protected void xor(byte[] bytesArray, int offset, int length) {
Utils.xor(this.bytes, this.offset, bytesArray, offset, length);
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Bytes other)) {
return false;
}

if (this.size() != other.size()) {
return false;
}

for (int i = 0; i < size(); i++) {
if (bytes[i + offset] != other.get(i)) {
return false;
}
}

return true;
}

@Override
protected int computeHashcode() {
int result = 1;
for (int i = 0; i < size(); i++) {
result = 31 * result + bytes[i + offset];
}
return result;
}
}
Loading
Loading