Skip to content

Commit 0350129

Browse files
authored
Use ThreadLocal caching for ExtendedRandom PRNG contexts (#1255)
Add ThreadLocal caching for native PRNG contexts used by ExtendedRandom. Each thread creates and reuses a PRNG context for supported DRBG algorithms. This avoids repeated EXTRAND_create calls when instances are created frequently. Benefits: - Reduce native allocation overhead - Reuse PRNG contexts per thread - Improve performance in RNG-heavy workloads Signed-off-by: Tao Liu <tao.liu@ibm.com>
1 parent 1df365e commit 0350129

3 files changed

Lines changed: 129 additions & 5 deletions

File tree

JenkinsfilePerformance

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ pipeline {
387387
'ibm.jceplus.jmh.PBEBenchmark', \
388388
'ibm.jceplus.jmh.PBKDF2Benchmark', \
389389
'ibm.jceplus.jmh.RandomBenchmark', \
390+
'ibm.jceplus.jmh.RandomNewInstanceBenchmark', \
390391
'ibm.jceplus.jmh.RSACipherBenchmark', \
391392
'ibm.jceplus.jmh.RSAKeyGeneratorBenchmark', \
392393
'ibm.jceplus.jmh.RSASignatureBenchmark', \

src/main/java/com/ibm/crypto/plus/provider/base/ExtendedRandom.java

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ public final class ExtendedRandom {
1616

1717
private OpenJCEPlusProvider provider;
1818
private NativeInterface nativeInterface;
19-
final long ockPRNGContextId;
19+
private final String algName;
20+
21+
private long ockPRNGContextId;
22+
private boolean usingThreadLocalContext = true;
23+
24+
private static final ThreadLocal<PRNGContextPointer> prngContextBufferSha256 = new ThreadLocal<PRNGContextPointer>();
25+
private static final ThreadLocal<PRNGContextPointer> prngContextBufferSha512 = new ThreadLocal<PRNGContextPointer>();
2026

2127
public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider provider)
2228
throws NativeException {
@@ -32,11 +38,35 @@ public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider pro
3238
}
3339

3440
private ExtendedRandom(String algName, OpenJCEPlusProvider provider) throws NativeException {
41+
this.algName = algName;
3542
this.provider = provider;
3643
this.nativeInterface = provider.isFIPS() ? NativeOCKAdapterFIPS.getInstance() : NativeOCKAdapterNonFIPS.getInstance();
37-
this.ockPRNGContextId = this.nativeInterface.EXTRAND_create(algName);
44+
this.ockPRNGContextId = getPRNGContext(algName);
45+
}
46+
47+
private long getPRNGContext(String algName) throws NativeException {
48+
PRNGContextPointer prngCtx = null;
49+
ThreadLocal<PRNGContextPointer> prngCtxBuffer = null;
50+
51+
switch (algName) {
52+
case "SHA256":
53+
prngCtxBuffer = prngContextBufferSha256;
54+
break;
55+
case "SHA512":
56+
prngCtxBuffer = prngContextBufferSha512;
57+
break;
58+
default:
59+
throw new IllegalArgumentException(
60+
"Unsupported HASHDRBG algorithm: " + algName);
61+
}
62+
63+
prngCtx = prngCtxBuffer.get();
64+
if (prngCtx == null) {
65+
prngCtx = new PRNGContextPointer(algName, this.nativeInterface, this.provider);
66+
prngCtxBuffer.set(prngCtx);
67+
}
3868

39-
this.provider.registerCleanable(this, cleanOCKResources(ockPRNGContextId, nativeInterface));
69+
return prngCtx.getCtx();
4070
}
4171

4272
public synchronized void nextBytes(byte[] bytes) throws NativeException {
@@ -55,22 +85,48 @@ public synchronized void setSeed(byte[] seed) throws NativeException {
5585
}
5686

5787
if (seed.length > 0) {
88+
createInstanceContextForReSeed();
5889
this.nativeInterface.EXTRAND_setSeed(ockPRNGContextId, seed);
5990
}
6091
}
6192

62-
private Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) {
93+
private void createInstanceContextForReSeed() throws NativeException {
94+
if (!usingThreadLocalContext) {
95+
return;
96+
}
97+
98+
long instanceCtx = this.nativeInterface.EXTRAND_create(algName);
99+
this.ockPRNGContextId = instanceCtx;
100+
this.usingThreadLocalContext = false;
101+
102+
this.provider.registerCleanable(this, cleanOCKResources(instanceCtx, nativeInterface));
103+
}
104+
105+
private static Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) {
63106
return () -> {
64107
try {
65108
if (ockPRNGContextId != 0) {
66109
nativeInterface.EXTRAND_delete(ockPRNGContextId);
67110
}
68111
} catch (Exception e) {
69112
if (OpenJCEPlusProvider.getDebug() != null) {
70-
OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning : " + e.getMessage());
113+
OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning: " + e.getMessage());
71114
e.printStackTrace();
72115
}
73116
}
74117
};
75118
}
119+
120+
private static final class PRNGContextPointer {
121+
final long prngCtx;
122+
123+
PRNGContextPointer(String algName, NativeInterface nativeInterface, OpenJCEPlusProvider provider) throws NativeException {
124+
this.prngCtx = nativeInterface.EXTRAND_create(algName);
125+
provider.registerCleanable(this, ExtendedRandom.cleanOCKResources(this.prngCtx, nativeInterface));
126+
}
127+
128+
long getCtx() {
129+
return this.prngCtx;
130+
}
131+
}
76132
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright IBM Corp. 2026, 2026
3+
*
4+
* This code is free software; you can redistribute it and/or modify it
5+
* under the terms provided by IBM in the LICENSE file that accompanied
6+
* this code, including the "Classpath" Exception described therein.
7+
*/
8+
9+
package ibm.jceplus.jmh;
10+
11+
import java.security.SecureRandom;
12+
import java.util.concurrent.TimeUnit;
13+
import org.openjdk.jmh.annotations.Benchmark;
14+
import org.openjdk.jmh.annotations.BenchmarkMode;
15+
import org.openjdk.jmh.annotations.Measurement;
16+
import org.openjdk.jmh.annotations.Mode;
17+
import org.openjdk.jmh.annotations.OutputTimeUnit;
18+
import org.openjdk.jmh.annotations.Param;
19+
import org.openjdk.jmh.annotations.Scope;
20+
import org.openjdk.jmh.annotations.Setup;
21+
import org.openjdk.jmh.annotations.State;
22+
import org.openjdk.jmh.annotations.Warmup;
23+
import org.openjdk.jmh.runner.Runner;
24+
import org.openjdk.jmh.runner.RunnerException;
25+
import org.openjdk.jmh.runner.options.Options;
26+
27+
@BenchmarkMode(Mode.Throughput)
28+
@OutputTimeUnit(TimeUnit.SECONDS)
29+
@State(Scope.Benchmark)
30+
@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS)
31+
@Measurement(iterations = 4, time = 30, timeUnit = TimeUnit.SECONDS)
32+
public class RandomNewInstanceBenchmark extends JMHBase {
33+
34+
@Param({"16", "2048", "32768"})
35+
private int payloadSize;
36+
37+
private byte[] payload;
38+
39+
private String algorithm;
40+
private String provider;
41+
42+
@Param({"SHA256DRBG|OpenJCEPlus", "SHA512DRBG|OpenJCEPlus", "SHA1PRNG|SUN", "DRBG|SUN"})
43+
private String randomToTest;
44+
45+
@Setup
46+
public void setup() throws Exception {
47+
String[] algAndProvider = randomToTest.split("\\|");
48+
algorithm = algAndProvider[0];
49+
provider = algAndProvider[1];
50+
super.setup(provider);
51+
payload = new byte[payloadSize];
52+
}
53+
54+
@Benchmark
55+
public byte[] runSecureRandom() throws Exception {
56+
SecureRandom random = SecureRandom.getInstance(algorithm, provider);
57+
random.nextBytes(payload);
58+
return payload;
59+
}
60+
61+
public static void main(String[] args) throws RunnerException {
62+
String testSimpleName = RandomNewInstanceBenchmark.class.getSimpleName();
63+
Options opt = optionsBuild(testSimpleName, testSimpleName);
64+
65+
new Runner(opt).run();
66+
}
67+
}

0 commit comments

Comments
 (0)