I would like to implement a simple rate limiter in java to learn how to use jmh. A simple github project is created at 'https://github.com/William1104/rate-limiter'
Interestingly, the throughput of some implementations (with stamplock) are impacted when '-XX:+UseLWPSynchronization' option is enabled. The benchmark test was performed on a Windows machine and I expect it has no impact to non-Solaris system. However, the testing result showing differently. May I know anyone may help me to understand what happen exactly?
Here are testing result on my machine as a reference:
With option: -server, -XX:+UnlockDiagnosticVMOptions, -XX:+UseNUMA
| Benchmark | (rateLimiterType) | Mode | Cnt | Score | Error | Units |
|---|---|---|---|---|---|---|
| RaterLimiterBenchmark.thread_1 | StampLockLongArrayRateLimiter | thrpt | 90 | 21487.385 | ▒ 1082.163 | ops/ms |
| RaterLimiterBenchmark.thread_1 | StampLockInstantArrayRateLimiter | thrpt | 90 | 13162.330 | ▒ 1585.555 | ops/ms |
| RaterLimiterBenchmark.thread_1 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 15362.934 | ▒ 227.704 | ops/ms |
| RaterLimiterBenchmark.thread_1 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 17281.675 | ▒ 2148.057 | ops/ms |
| RaterLimiterBenchmark.thread_10 | StampLockLongArrayRateLimiter | thrpt | 90 | 6868.653 | ▒ 146.372 | ops/ms |
| RaterLimiterBenchmark.thread_10 | StampLockInstantArrayRateLimiter | thrpt | 90 | 8189.747 | ▒ 335.517 | ops/ms |
| RaterLimiterBenchmark.thread_10 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 6643.004 | ▒ 103.568 | ops/ms |
| RaterLimiterBenchmark.thread_10 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 5252.975 | ▒ 190.363 | ops/ms |
| RaterLimiterBenchmark.thread_100 | StampLockLongArrayRateLimiter | thrpt | 90 | 7352.890 | ▒ 2109.446 | ops/ms |
| RaterLimiterBenchmark.thread_100 | StampLockInstantArrayRateLimiter | thrpt | 90 | 8675.814 | ▒ 922.653 | ops/ms |
| RaterLimiterBenchmark.thread_100 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 6509.368 | ▒ 157.212 | ops/ms |
| RaterLimiterBenchmark.thread_100 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 5042.867 | ▒ 192.971 | ops/ms |
With option: -server, -XX:+UnlockDiagnosticVMOptions, -XX:+UseNUMA, -XX:+UseLWPSynchronization
| Benchmark | (rateLimiterType) | Mode | Cnt | Score | Error | Units |
|---|---|---|---|---|---|---|
| RaterLimiterBenchmark.thread_1 | StampLockLongArrayRateLimiter | thrpt | 90 | 11383.198 | ▒ 353.921 | ops/ms |
| RaterLimiterBenchmark.thread_1 | StampLockInstantArrayRateLimiter | thrpt | 90 | 11666.918 | ▒ 842.426 | ops/ms |
| RaterLimiterBenchmark.thread_1 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 15696.852 | ▒ 371.078 | ops/ms |
| RaterLimiterBenchmark.thread_1 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 15357.617 | ▒ 650.846 | ops/ms |
| RaterLimiterBenchmark.thread_10 | StampLockLongArrayRateLimiter | thrpt | 90 | 6937.050 | ▒ 130.727 | ops/ms |
| RaterLimiterBenchmark.thread_10 | StampLockInstantArrayRateLimiter | thrpt | 90 | 8268.909 | ▒ 291.471 | ops/ms |
| RaterLimiterBenchmark.thread_10 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 9134.319 | ▒ 1208.998 | ops/ms |
| RaterLimiterBenchmark.thread_10 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 5294.341 | ▒ 225.995 | ops/ms |
| RaterLimiterBenchmark.thread_100 | StampLockLongArrayRateLimiter | thrpt | 90 | 8453.825 | ▒ 1075.312 | ops/ms |
| RaterLimiterBenchmark.thread_100 | StampLockInstantArrayRateLimiter | thrpt | 90 | 16297.921 | ▒ 611.255 | ops/ms |
| RaterLimiterBenchmark.thread_100 | SynchronizedLongArrayRateLimiter | thrpt | 90 | 12536.378 | ▒ 974.951 | ops/ms |
| RaterLimiterBenchmark.thread_100 | SynchronizedInstantArrayRateLimiter | thrpt | 90 | 9051.560 | ▒ 1303.856 | ops/ms |
There are the implementation of StampLockLongArrayRateLimiter and SynchronizedLongArrayRateLImiter:
package one.williamwong.ratelimiter;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.locks.StampedLock;
public class StampLockLongArrayRateLimiter implements IRateLimiter {
private final long duration;
private final long[] records;
private final StampedLock lock;
private int pointer;
public StampLockLongArrayRateLimiter(int maxInvokes, Duration duration) {
this.duration = duration.toNanos();
this.records = new long[maxInvokes];
this.lock = new StampedLock();
this.pointer = 0;
}
@Override public void acquire() {
final long stamp = lock.writeLock();
try {
final long now = System.nanoTime();
if (records[pointer] != 0) {
final long awayFromHead = now - records[pointer];
if (awayFromHead < duration) {
handleExcessLimit(records.length, Duration.ofNanos(awayFromHead));
}
}
records[pointer] = now;
pointer = (pointer + 1) % records.length;
} finally {
lock.unlockWrite(stamp);
}
}
@Override public void reset() {
final long stamp = lock.writeLock();
try {
Arrays.fill(records, 0);
this.pointer = 0;
} finally {
lock.unlockWrite(stamp);
}
}
}
package one.williamwong.ratelimiter;
import java.time.Duration;
import java.util.Arrays;
public class SynchronizedLongArrayRateLimiter implements IRateLimiter {
private final long duration;
private final long[] records;
private final Object lock;
private int pointer;
public SynchronizedLongArrayRateLimiter(int maxInvokes, Duration duration) {
this.duration = duration.toNanos();
this.records = new long[maxInvokes];
this.lock = new Object();
this.pointer = 0;
}
@Override
public void acquire() {
synchronized (lock) {
final long now = System.nanoTime();
if (records[pointer] != 0) {
final long awayFromHead = now - records[pointer];
if (awayFromHead < duration) {
handleExcessLimit(records.length, Duration.ofNanos(awayFromHead));
}
}
records[pointer] = now;
pointer = (pointer + 1) % records.length;
}
}
@Override public void reset() {
synchronized (lock) {
Arrays.fill(records, 0);
this.pointer = 0;
}
}
}