Skip to content

Commit

Permalink
Add benchmarks for a bit of fun comparing solana4j implementation for…
Browse files Browse the repository at this point in the history
… common cryptographic implementations compared with standard libraries
  • Loading branch information
ml-james committed Oct 4, 2024
1 parent d79aa1b commit 880bcb8
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 2 deletions.
17 changes: 15 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ sourceSets {
runtimeClasspath += sourceSets.main.output + configurations.testRuntimeClasspath
srcDir file('src/test-support/java')
}
resources.srcDir file('src/test-support/resources')
}
test {
java {
Expand All @@ -60,6 +59,13 @@ sourceSets {
}
resources.srcDir file('src/integration-test/resources')
}
jmh {
java {
compileClasspath += sourceSets.main.output + sourceSets.testSupport.output
runtimeClasspath += sourceSets.main.output + sourceSets.testSupport.output
srcDir file('src/jmh/java')
}
}
}

dependencies {
Expand All @@ -83,7 +89,6 @@ dependencies {
testImplementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1'
testImplementation 'org.slf4j:slf4j-api:2.0.13'
testImplementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
testImplementation 'org.bitcoinj:bitcoinj-core:0.16.3'

// integration test dependencies
integrationTestImplementation 'org.testcontainers:testcontainers:1.19.8'
Expand All @@ -96,6 +101,14 @@ dependencies {
integrationTestImplementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
integrationTestImplementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1'
integrationTestImplementation 'org.slf4j:slf4j-api:2.0.13'

// jmh test dependencies
jmhImplementation 'org.openjdk.jmh:jmh-core:1.37'
jmhImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
jmhImplementation 'net.i2p.crypto:eddsa:0.3.0'
jmhImplementation 'org.bitcoinj:bitcoinj-core:0.16.3'

jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
}

test {
Expand Down
46 changes: 46 additions & 0 deletions src/jmh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#### JMH Benchmarks

This `solana4j` library re-implements a few crucial pieces of functionality that are in standard crpytographic libraries,
mostly to avoid it requiring any direct dependencies. We thought it would be fun to compare the performance of our implementations
to that of these standard libraries.

To run the JMH benchmarks, it is easiest to run via `IntelliJ` with the `JMH Java Microbenchmark Harness` plugin installed.

##### Base58 Encoding

Dependency Replaced: `org.bitcoinj:bitcoinj-core:0.16.3`
Benchmark Written: `Base58EncodingBenchmark`

```text
Benchmark Mode Cnt Score Error Units
Base58EncodingBenchmark.base58DecodeBitcoinjImplementation thrpt 10 804003.990 ± 8733.718 ops/s
Base58EncodingBenchmark.base58DecodeSolana4jImplementation thrpt 10 805712.402 ± 10421.606 ops/s
Base58EncodingBenchmark.base58EncodeBitcoinjImplementation thrpt 10 188832.893 ± 2511.813 ops/s
Base58EncodingBenchmark.base58EncodeSolana4jImplementation thrpt 10 188675.567 ± 1932.977 ops/s
```

##### Finding whether a point lies on the Ed25519 curve

Dependency Replaced: `net.i2p.crypto:eddsa:0.3.0`
Benchmark Written: `EddsaPointOnCurveBenchmark`

```text
Benchmark Mode Cnt Score Error Units
EddsaPointOnCurveBenchmark.pointNotOnCurveEddsaImplementation thrpt 10 148322.070 ± 2213.306 ops/s
EddsaPointOnCurveBenchmark.pointNotOnCurveSolana4jImplementation thrpt 10 83202.803 ± 3213.023 ops/s
EddsaPointOnCurveBenchmark.pointOnCurveEddsaImplementation thrpt 10 83929.318 ± 3607.478 ops/s
EddsaPointOnCurveBenchmark.pointOnCurveSolana4jImplementation thrpt 10 84799.239 ± 5135.192 ops/s
```

#### Sha256 Hashing

Dependency Replaced: `org.bitcoinj:bitcoinj-core:0.16.3`
Benchmark Written: `Sha256HashingBenchmark`

```text
Benchmark Mode Cnt Score Error Units
Sha256HashingBenchmark.sha256HashBitcoinjImplementation thrpt 10 9082306.173 ± 56286.482 ops/s
Sha256HashingBenchmark.sha256HashSolana4jImplementation thrpt 10 9184423.015 ± 18583.452 ops/s
```


54 changes: 54 additions & 0 deletions src/jmh/java/com/lmax/solana4j/Base58EncodingBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.lmax.solana4j;

import com.lmax.solana4j.util.Base58;
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.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@Threads(1)
@State(Scope.Thread)
public class Base58EncodingBenchmark
{
private static final String BASE58_STRING = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final byte[] BYTES = BASE58_STRING.getBytes(StandardCharsets.UTF_8);

@Benchmark
public void base58DecodeSolana4jImplementation(final Blackhole bh)
{
bh.consume(Base58.decode(BASE58_STRING));
}

@Benchmark
public void base58EncodeSolana4jImplementation(final Blackhole bh)
{
bh.consume(Base58.encode(BYTES));
}

@Benchmark
public void base58DecodeBitcoinjImplementation(final Blackhole bh)
{
bh.consume(org.bitcoinj.core.Base58.decode(BASE58_STRING));
}

@Benchmark
public void base58EncodeBitcoinjImplementation(final Blackhole bh)
{
bh.consume(org.bitcoinj.core.Base58.encode(BYTES));
}
}
80 changes: 80 additions & 0 deletions src/jmh/java/com/lmax/solana4j/EddsaPointOnCurveBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.lmax.solana4j;

import com.lmax.solana4j.util.Ed25519;
import com.lmax.solana4j.util.TestKeyPairGenerator;
import net.i2p.crypto.eddsa.math.GroupElement;
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.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.TimeUnit;

import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@Threads(1)
@State(Scope.Thread)
public class EddsaPointOnCurveBenchmark
{
private static final byte[] POINT_NOT_ON_CURVE = new byte[]
{
1, 5, 9, 3, 6, 9, 3, 6, 9, 3,
2, 6, 1, 4, 7, 1, 4, 7, 1, 4,
3, 7, 2, 5, 8, 2, 5, 8, 2, 5,
4, 8
};

private static final byte[] POINT_ON_CURVE = TestKeyPairGenerator.generateSolanaKeyPair().getPublicKeyBytes();

@Benchmark
public void pointOnCurveSolana4jImplementation(final Blackhole bh)
{
bh.consume(Ed25519.isOnCurve(POINT_ON_CURVE));
}

@Benchmark
public void pointNotOnCurveSolana4jImplementation(final Blackhole bh)
{
bh.consume(Ed25519.isOnCurve(POINT_NOT_ON_CURVE));
}

@Benchmark
public void pointOnCurveEddsaImplementation(final Blackhole bh)
{
try
{
final GroupElement point = ED_25519_CURVE_SPEC.getCurve().createPoint(POINT_ON_CURVE, false);
bh.consume(point.isOnCurve());
}
catch (final Exception e)
{
bh.consume(true);
}
}

@Benchmark
public void pointNotOnCurveEddsaImplementation(final Blackhole bh)
{
try
{
final GroupElement point = ED_25519_CURVE_SPEC.getCurve().createPoint(POINT_NOT_ON_CURVE, false);
bh.consume(point.isOnCurve());
}
catch (final Exception e)
{
bh.consume(false);
}
}
}
43 changes: 43 additions & 0 deletions src/jmh/java/com/lmax/solana4j/Sha256HashingBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.lmax.solana4j;

import com.lmax.solana4j.util.Sha256;
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.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@Threads(1)
@State(Scope.Thread)
public class Sha256HashingBenchmark
{
private static final String RANDOM_STRING = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final byte[] BYTES = RANDOM_STRING.getBytes(StandardCharsets.UTF_8);

@Benchmark
public void sha256HashSolana4jImplementation(final Blackhole bh)
{
bh.consume(Sha256.hash(BYTES));
}

@Benchmark
public void sha256HashBitcoinjImplementation(final Blackhole bh)
{
bh.consume(org.bitcoinj.core.Sha256Hash.hash(BYTES));
}

}

0 comments on commit 880bcb8

Please sign in to comment.