Skip to content

Conversation

@2tsumo-hitori
Copy link
Contributor

@2tsumo-hitori 2tsumo-hitori commented Mar 16, 2025

I believe that the getStreamBridgeFunction method operates in a scenario where the map does not contain a large number of entries, and the cache hit rate is likely to exceed 99%.

Instead of using ReentrantLock to lock the entire map on every access, it would be more efficient to leverage ConcurrentHashMap.computeIfAbsent(),
which allows direct retrieval of existing keys without locking and only acquires a lock when a key is missing to safely add a new entry.

This approach minimizes unnecessary contention, improves concurrency, and optimizes performance in high-throughput environments.

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class MapBenchmark {

    private static final int CACHE_SIZE = 10;
    private static final int ITERATIONS = 100_000;

    private Map<Integer, String> lockedCache;
    private ConcurrentHashMap<Integer, String> concurrentCache;
    private ReentrantLock lock;
    private int[] keys;

    @Setup(Level.Trial)
    public void setup() {
        lockedCache = new HashMap<>(CACHE_SIZE);
        concurrentCache = new ConcurrentHashMap<>(CACHE_SIZE);
        lock = new ReentrantLock();
        keys = new int[CACHE_SIZE];

        for (int i = 0; i < CACHE_SIZE; i++) {
            keys[i] = i;
        }

        for (int key : keys) {
            String value = expensiveOperation(key);
            lockedCache.put(key, value);
            concurrentCache.put(key, value);
        }
    }

    public String expensiveOperation(int key) {
        return "Value-" + key;
    }

    @Benchmark
    @Threads(8)
    public void testReentrantLock() {
        for (int i = 0; i < ITERATIONS; i++) {
            int key = keys[ThreadLocalRandom.current().nextInt(CACHE_SIZE)];
            lock.lock();
            try {
                if (lockedCache.containsKey(key)) {
                    lockedCache.get(key);
                } else {
                    lockedCache.put(key, expensiveOperation(key));
                }
            } finally {
                lock.unlock();
            }
        }
    }

    @Benchmark
    @Threads(8)
    public void testConcurrentHashMap() {
        for (int i = 0; i < ITERATIONS; i++) {
            int key = keys[ThreadLocalRandom.current().nextInt(CACHE_SIZE)];
            concurrentCache.computeIfAbsent(key, this::expensiveOperation);
        }
    }

  public static void main(String[] args) throws RunnerException {
          Options opt = new OptionsBuilder()
                  .include(MapBenchmark.class.getSimpleName())
                  .warmupIterations(5)
                  .measurementIterations(10)
                  .forks(1)
                  .build();
          new Runner(opt).run();
      }
}

Benchmark Mode Cnt Score Error Units
MapBenchmark.testConcurrentHashMap avgt 10 7.363 ± 11.278 ms/op
MapBenchmark.testReentrantLock avgt 10 27.336 ± 0.597 ms/op

To validate this optimization, I conducted a performance benchmark comparing ConcurrentHashMap.computeIfAbsent() and ReentrantLock.
The results clearly demonstrate that ConcurrentHashMap significantly outperforms the lock-based approach

to improve performance in StreamBridge.send() method.

- Avoids unnecessary locking overhead
- Improves concurrency and reduces contention
- Enhances throughput for high-load scenarios

Signed-off-by: 2tsumo-hitori <audghks1996@naver.com>
@2tsumo-hitori 2tsumo-hitori reopened this Mar 17, 2025
@olegz olegz added this to the 4.3.0 milestone Mar 24, 2025
@olegz olegz self-assigned this Mar 24, 2025
@olegz
Copy link
Contributor

olegz commented Mar 24, 2025

Merged bb3324a

@olegz olegz closed this Mar 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants