Skip to content

Instantly share code, notes, and snippets.

@fabiolimace
Created December 9, 2021 04:07
Show Gist options
  • Save fabiolimace/71653c155ba465d78eee1c519bd02f3d to your computer and use it in GitHub Desktop.
Save fabiolimace/71653c155ba465d78eee1c519bd02f3d to your computer and use it in GitHub Desktop.
Benchmark to compare a fixed pool of SecureRandom to a thread local of SecureRandom in Java

How to prepare the benchmark files on Linux:

BASE="bench"

mkdir $BASE
cp run.sh $BASE/
cp run.bat $BASE/
cp pom.xml $BASE/
cp README.md $BASE/

mkdir --parents $BASE/src/main/java/benchmark
cp Throughput.java $BASE/src/main/java/benchmark/

To execute the benchmark, run the script $BASE/run.sh.

<!--
Copyright (c) 2014, Oracle America, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Oracle nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>benchmark</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JMH benchmark sample: Java</name>
<!--
This is the demo/sample template build script for building Java benchmarks with JMH.
Edit as needed.
-->
<properties>
<!--
JMH version to use with this project.
-->
<jmh.version>1.32</jmh.version>
<!--
Java source/target to use for compilation.
-->
<javac.target>1.8</javac.target>
<!--
Name of the benchmark Uber-JAR to generate.
-->
<uberjar.name>benchmarks</uberjar.name>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerVersion>${javac.target}</compilerVersion>
<source>${javac.target}</source>
<target>${javac.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<!--
Shading signed JARs will fail without this.
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
-->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.1</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
@ECHO OFF
REM compile the benchmark project
CALL mvn validate
CALL mvn clean install
REM run the benchmark
CALL java -jar target/benchmarks.jar
@ECHO ON
#!/bin/bash
# compile the benchmark project
mvn validate
mvn clean install
# run the benchmark
/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar target/benchmarks.jar
# java -jar target/benchmarks.jar
package benchmark;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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.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;
@Fork(1)
@Threads(100)
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Measurement(iterations = 3)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Throughput {
// the number of processos on your PC
private static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
// a singleton random generator
private static final Random RANDOM_SINGLETON = getSecureRandom();
// a thread local of random generators
private static final ThreadLocal<Random> RANDOM_THREAD_LOCAL = ThreadLocal.withInitial(Throughput::getSecureRandom);
// a fixed pool of random generators
private static final Random[] RANDOM_FIXED_POOL = new Random[PROCESSORS];
static {
for (int i = 0; i < PROCESSORS; i++) {
RANDOM_FIXED_POOL[i] = getSecureRandom();
}
}
@Benchmark
public long singleton() {
return RANDOM_SINGLETON.nextLong();
}
@Benchmark
public long fixedpool() {
return current().nextLong();
}
@Benchmark
public long threadlocal() {
return RANDOM_THREAD_LOCAL.get().nextLong();
}
/*** HELPER METHODS ***/
private static Random current() {
// calculate the factory index given the current thread ID
final int index = (int) Thread.currentThread().getId() % PROCESSORS;
return RANDOM_FIXED_POOL[index];
}
private static Random getSecureRandom() {
// SHA1PRNG or DRBG (JDK9+)
String algorithm = "SHA1PRNG";
try {
return SecureRandom.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
}
@fabiolimace
Copy link
Author

About

These benchmark files were created to compare a fixed pool of SecureRandom to a thread local SecureRandom.

The thread pool SIZE is equal to the number of processors (or cores) available to the runtime. For example, if there's only 1 processor available, the pool size will be 1. If there are 16 processors available, the pool size will be 16.

The current INDEX in the thread pool is calculated with this formula: INDEX = CURRENT_THREAD_ID % POOL_SIZE.

Results

A benchmark using a high number of threads (100) showed that the fixed pool performs almost the same as the thread local to generate random numbers using the SHA1PRNG algorithm.

-----------------------------------------------------------------
Benchmark                Mode  Cnt      Score      Error   Units
-----------------------------------------------------------------
Throughput.singleton    thrpt    3   3020,402 ±  449,643  ops/ms
Throughput.fixedpool    thrpt    3  14639,183 ± 1205,735  ops/ms  (NEW)
Throughput.threadlocal  thrpt    3  15163,027 ±  905,890  ops/ms  (OLD)
-----------------------------------------------------------------

Benchmark settings:

  • Number of threads: 100
  • SecureRandom algorithm: SHA1PRNG
  • Computer: JVM 8, Ubuntu 20.04, CPU i5-3330, 8G RAM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment