Path: blob/master/test/micro/org/openjdk/bench/java/lang/ThreadOnSpinWaitProducerConsumer.java
66646 views
/*1* Copyright (c) 2021, Amazon.com Inc. or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/22package org.openjdk.bench.java.lang;2324import org.openjdk.jmh.annotations.Benchmark;25import org.openjdk.jmh.annotations.BenchmarkMode;26import org.openjdk.jmh.annotations.Level;27import org.openjdk.jmh.annotations.Mode;28import org.openjdk.jmh.annotations.OutputTimeUnit;29import org.openjdk.jmh.annotations.Param;30import org.openjdk.jmh.annotations.Scope;31import org.openjdk.jmh.annotations.Setup;32import org.openjdk.jmh.annotations.State;33import org.openjdk.jmh.annotations.Threads;3435import org.openjdk.jmh.infra.Blackhole;3637import java.math.BigInteger;38import java.util.Random;39import java.util.concurrent.TimeUnit;40import java.util.function.BooleanSupplier;4142/**43* This microbenchmark models producer-consumer.44*45* The microbenchmark uses two thread: 1 for a producer, 1 for a consumer.46* The microbenchmark uses BigInteger to have latencies of producing/consuming47* data comparable with synchronization operations.48*49* Thread.onSpinWait is used in a spin loop which is used to avoid heavy locks.50* In the spin loop volatile fields are checked. To reduce overhead accessing them51* they are only checked after a number of iterations.52*/53@BenchmarkMode(Mode.AverageTime)54@OutputTimeUnit(TimeUnit.MICROSECONDS)55@State(Scope.Benchmark)56@Threads(1)57public class ThreadOnSpinWaitProducerConsumer {58@Param({"100"})59public int maxNum;6061@Param({"125"})62public int spinNum;6364@Param({"10"})65public int checkSpinCondAfterIters;6667@Param({"256"})68public int dataBitLength;6970private Thread threadProducer;71private Thread threadConsumer;72private Object monitor;7374private BigInteger a;75private BigInteger b;76private Blackhole bh;7778private volatile int dataId;79private volatile int seenDataId;8081private int producedDataCount;82private int consumedDataCount;8384private void produceData() {85if (!isDataSeen()) {86return;87}8889b = a.not();90++dataId;91++producedDataCount;92}9394private void consumeData() {95if (isDataSeen()) {96return;97}98bh.consume(a.equals(b.not()));99seenDataId = dataId;100++consumedDataCount;101}102103private boolean isDataSeen() {104return seenDataId == dataId;105}106107private boolean isNewData() {108return seenDataId != dataId;109}110111private boolean spinWaitForCondition(int spinNum, BooleanSupplier cond) {112for (int i = 0; i < spinNum; ++i) {113if ((i % checkSpinCondAfterIters) == 0 && cond.getAsBoolean()) {114return true;115}116Thread.onSpinWait();117}118return cond.getAsBoolean();119}120121void produce() {122try {123while (dataId < maxNum) {124if (spinWaitForCondition(this.spinNum, this::isDataSeen)) {125synchronized (monitor) {126produceData();127monitor.notify();128}129} else {130synchronized (monitor) {131while (!isDataSeen()) {132monitor.wait();133}134135produceData();136monitor.notify();137}138}139}140} catch (InterruptedException e) {}141}142143void consume() {144try {145for (;;) {146if (spinWaitForCondition(this.spinNum, this::isNewData)) {147synchronized (monitor) {148consumeData();149monitor.notify();150}151} else {152synchronized (monitor) {153while (isDataSeen()) {154monitor.wait();155}156157consumeData();158monitor.notify();159}160}161}162} catch (InterruptedException e) {}163}164165@Setup(Level.Trial)166public void setup01() {167Random rnd = new Random(111);168a = BigInteger.probablePrime(dataBitLength, rnd);169monitor = new Object();170}171172@Setup(Level.Invocation)173public void setup02() {174threadProducer = new Thread(this::produce);175threadConsumer = new Thread(this::consume);176}177178@Benchmark179public void trial(Blackhole bh) throws Exception {180this.bh = bh;181producedDataCount = 0;182consumedDataCount = 0;183dataId = 0;184seenDataId = 0;185threadProducer.start();186threadConsumer.start();187threadProducer.join();188189synchronized (monitor) {190while (!isDataSeen()) {191monitor.wait();192}193}194threadConsumer.interrupt();195196if (producedDataCount != maxNum) {197throw new RuntimeException("Produced: " + producedDataCount + ". Expected: " + maxNum);198}199if (producedDataCount != consumedDataCount) {200throw new RuntimeException("produced != consumed: " + producedDataCount + " != " + consumedDataCount);201}202}203}204205206