对JcTools中MPMC和SPMC两种队列的性能进行测试、计较并记录。
前言
记录一下使用JcTools的MPMC和SPMC两种无锁队列的性能比较。
一、搭建项目
maven依赖
<dependency>
<groupId>org.jctools</groupId>
<artifactId>jctools-core</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
二、编写代码
1. 引入库
代码如下(示例):
Main
public class Main {
public static final String JCTOOLS_TYPE = "jctools.type";
public static final String JCTOOLS_MESSAGE_SIZE = "jctools.message.size";
public static final String JCTOOLS_QUEUE_SIZE = "jctools.queue.size";
public static final String JCTOOLS_PRODUCER_THREAD_MIN = "jctools.producer.thread.min";
public static final String JCTOOLS_PRODUCER_THREAD_MAX = "jctools.producer.thread.max";
public static final String JCTOOLS_PRODUCER_QUEUE_SIZE = "jctools.producer.queue.size";
public static final String JCTOOLS_CONSUMER_QUEUE_SIZE = "jctools.consumer.queue.size";
public static final String JCTOOLS_CONSUMER_THREAD_MIN = "jctools.consumer.thread.min";
public static final String JCTOOLS_CONSUMER_THREAD_MAX = "jctools.consumer.thread.max";
public static final String JCTOOLS_CONSUMER_SLEEP = "jctools.consumer.sleep";
public static void main(String[] args) {
String type = System.getProperty(JCTOOLS_TYPE);
Integer messageSize = Integer.valueOf(System.getProperty(JCTOOLS_MESSAGE_SIZE));
Integer queueSize = Integer.valueOf(System.getProperty(JCTOOLS_QUEUE_SIZE));
Integer produceMin = Integer.valueOf(System.getProperty(JCTOOLS_PRODUCER_THREAD_MIN));
Integer produceMax = Integer.valueOf(System.getProperty(JCTOOLS_PRODUCER_THREAD_MAX));
Integer produceQueueSize = Integer.valueOf(System.getProperty(JCTOOLS_PRODUCER_QUEUE_SIZE));
Integer consumeQueueSize = Integer.valueOf(System.getProperty(JCTOOLS_CONSUMER_QUEUE_SIZE));
Integer consumeMin = Integer.valueOf(System.getProperty(JCTOOLS_CONSUMER_THREAD_MIN));
Integer consumeMax = Integer.valueOf(System.getProperty(JCTOOLS_CONSUMER_THREAD_MAX));
Integer consumeSleep = Integer.valueOf(System.getProperty(JCTOOLS_CONSUMER_SLEEP));
Result result = null;
if ("mpmc".equals(type)) {
System.out.println("===========================【" + type + "】测试开始===========================");
MpmcTest mpmcTest = MpmcTest.INSTANCE;
mpmcTest.setMessageSize(messageSize);
mpmcTest.setQueueSize(queueSize);
mpmcTest.setConsumerExecutor(new ThreadPoolExecutor(consumeMin, consumeMax, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(consumeQueueSize), new ThreadPoolExecutor.CallerRunsPolicy()));
mpmcTest.setProducerExecutor(new ThreadPoolExecutor(produceMin, produceMax, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(produceQueueSize), new ThreadPoolExecutor.CallerRunsPolicy()));
mpmcTest.setSleepTime(consumeSleep);
result = mpmcTest.test();
}
if ("spmc".equals(type)) {
System.out.println("===========================【" + type + "】测试开始===========================");
SpmcTest spmcTest = SpmcTest.INSTANCE;
spmcTest.setMessageSize(messageSize);
spmcTest.setQueueSize(queueSize);
spmcTest.setConsumerExecutor(new ThreadPoolExecutor(consumeMin, consumeMax, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(consumeQueueSize), new ThreadPoolExecutor.CallerRunsPolicy()));
spmcTest.setProducerExecutor(Executors.newSingleThreadExecutor());
spmcTest.setSleepTime(consumeSleep);
result = spmcTest.test();
}
if (result != null) {
System.out.println("===========================【" + type + "】测试结束===========================");
System.out.println("发送成功数:\t" + result.getProduceSuccess());
System.out.println("发送失败数:\t" + result.getProduceFail());
System.out.println("消费成功数:\t" + result.getConsumeSuccess());
System.out.println("消费失败数:\t" + result.getConsumeFail());
System.out.println("总耗时:\t" + result.getDurTime()/1000000);
}
}
}
Result
@Data
public class Result {
private Integer consumeSuccess = 0;
private Integer consumeFail = 0;
private Integer produceSuccess = 0;
private Integer produceFail = 0;
private Long durTime = 0L;
}
MpmcTest
@Data
public class MpmcTest {
private MpmcTest() {
}
public static final MpmcTest INSTANCE = new MpmcTest();
private ThreadPoolExecutor producerExecutor;
private ThreadPoolExecutor consumerExecutor;
private Integer messageSize;
private Integer queueSize;
private Integer sleepTime;
private String prefix = "mpmc_thread_";
private static AtomicInteger consumeSuccess = new AtomicInteger(0);
private static AtomicInteger consumeFail = new AtomicInteger(0);
private static AtomicInteger produceSuccess = new AtomicInteger(0);
private static AtomicInteger produceFail = new AtomicInteger(0);
public Result test() {
MpmcArrayQueue<Long> queue = new MpmcArrayQueue<>(this.queueSize);
// CountDownLatch countDownLatch = new CountDownLatch(messageSize);
long startTime = System.nanoTime();
for (int i = 0; i < messageSize; i++) {
Long index = (long) i;
producerExecutor.execute(() -> {
while (!queue.offer(index)) {
System.out.println("===================任务发布失败,线程:" + prefix + index);
produceFail.getAndIncrement();
}
System.out.println("===================任务发布成功,线程:" + prefix + index);
produceSuccess.getAndIncrement();
consumerExecutor.execute(() -> {
Long retIndex = queue.poll();
if (retIndex == null) {
System.out.println(">>>>>>>>>>>>>>>>>>消费失败,线程:" + prefix + retIndex);
consumeFail.getAndIncrement();
} else {
System.out.println("消费成功,线程:" + prefix + retIndex);
consumeSuccess.getAndIncrement();
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (messageSize <= (consumeSuccess.get() + consumeFail.get())) {
consumerExecutor.shutdown();
}
// countDownLatch.countDown();
});
});
}
Result result = new Result();
try {
// countDownLatch.await(120, TimeUnit.SECONDS);
// System.out.println("countDown"+countDownLatch.getCount());
// consumerExecutor.shutdownNow();
if (consumerExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
result.setConsumeSuccess(consumeSuccess.get());
result.setConsumeFail(consumeFail.get());
result.setProduceSuccess(produceSuccess.get());
result.setProduceFail(produceFail.get());
result.setDurTime(System.nanoTime() - startTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
SpmcTest
@Data
public class SpmcTest {
private SpmcTest() {
}
public static final SpmcTest INSTANCE = new SpmcTest();
private ExecutorService producerExecutor;
private ExecutorService consumerExecutor;
private Integer messageSize;
private Integer queueSize;
private Integer sleepTime;
private String prefix = "spmc_thread_";
public Result test() {
MpmcArrayQueue<Long> queue = new MpmcArrayQueue<>(this.queueSize);
AtomicInteger consumeSuccess = new AtomicInteger();
AtomicInteger consumeFail = new AtomicInteger();
AtomicInteger produceSuccess = new AtomicInteger();
AtomicInteger produceFail = new AtomicInteger();
long startTime = System.nanoTime();
for (int i = 0; i < messageSize; i++) {
Long index = (long) i;
producerExecutor.execute(() -> {
while (!queue.offer(index)) {
produceFail.getAndIncrement();
}
produceSuccess.getAndIncrement();
consumerExecutor.execute(() -> {
Long retIndex = queue.poll();
if (retIndex == null) {
System.out.println("===================消费失败,线程:" + prefix + retIndex);
consumeFail.getAndIncrement();
} else {
System.out.println("消费成功,线程:" + prefix + retIndex);
consumeSuccess.getAndIncrement();
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (messageSize <= (consumeSuccess.get() + consumeFail.get())) {
consumerExecutor.shutdown();
}
});
});
}
Result result = new Result();
try {
if (consumerExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
result.setConsumeSuccess(consumeSuccess.get());
result.setConsumeFail(consumeFail.get());
result.setProduceSuccess(produceSuccess.get());
result.setProduceFail(produceFail.get());
result.setDurTime(System.nanoTime() - startTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
三、构建脚本并测试
1. 构建启动脚本
在Idea里添加如下配置
-Djctools.type=mpmc -Djctools.message.size=100000 -Djctools.queue.size=20000 -Djctools.producer.thread.min=10 -Djctools.producer.thread.max=40 -Djctools.producer.queue.size=10000 -Djctools.consumer.thread.min=20 -Djctools.consumer.thread.max=80 -Djctools.consumer.queue.size=20000 -Djctools.consumer.sleep=5
2. 更换条件,获取结果
方案一
数据量 100000 queueSize 10000 生产线程:10 40 10000 消费线程:20 80 20000 消费时间 5ms
SPMC 6793 qps 14,705 cpu 1% 内存 10m
MPMC 5327 qps 18,867 cpu 1% 内存 10m
方案二
数据量 1000000 queueSize 10000 生产线程:10 40 10000 消费线程:20 80 20000 消费时间 5ms
SPMC 69511 qps 14,388 cpu 3% 内存 170m
MPMC 47436 qps 21,097 cpu 1% 内存 100m
方案三
数据量 1000000 queueSize 10000 生产线程:10 40 10000 消费线程:200 800 20000 消费时间 5ms
SPMC 12492 qps 80,000 cpu 2% 内存200m
MPMC 16457 qps 62,500 cpu 2% 内存150m
方案四
数据量 10000000 queueSize 10000 生产线程:10 40 10000 消费线程:200 800 20000 消费时间 5ms
SPMC 108148 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2833m 均值2000m
MPMC 142194 qps 70,373 cpu 2% 内存150m
方案五
数据量 10000000 queueSize 10000 生产线程:100 400 10000 消费线程:200 800 20000 消费时间 5ms
SPMC 107203 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2833m 均值2000m
MPMC 161223 qps 161223 cpu 2% 内存150m
方案六
数据量 10000000 queueSize 10000 生产线程:100 400 100000 消费线程:200 800 20000 消费时间 5ms
SPMC 107203 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2833m 均值2000m
MPMC 145828 qps 161223 cpu 2% 内存150m
方案七
数据量 10000000 queueSize 10000 生产线程:100 400 100000 消费线程:200 800 200000 消费时间 5ms
SPMC 107203 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2833m 均值2000m
MPMC N/A qps N/A cpu 2% 内存150m 大量线程发布失败,93s执行470000
方案七
数据量 10000000 queueSize 100000 生产线程:100 400 100000 消费线程:200 800 200000 消费时间 5ms
SPMC 286462 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2425m 均值2000m 出现大量发送失败,但最终都成功;发送成功数10000000;发送失败数2084979727
MPMC N/A qps N/A cpu 2% 内存150m 大量线程发布失败,5‘33s执行1223724
方案八
数据量 10000000 queueSize 40000 生产线程400 1000 20000 消费线程:1000 4000 40000 消费时间 5ms
SPMC 151742 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2825m 均值2000m
MPMC 208681 qps cpu 2% 内存150m
方案九
数据量 10000000 queueSize 10000 生产线程400 1000 20000 消费线程:1000 4000 40000 消费时间 5ms
SPMC 144742 qps 92,592 cpu 初始8%(4s) 后2% 内存 峰值2825m 均值2000m 出现大量发送失败,但最终都成功;发送成功数10000000;发送失败数-1276524988
MPMC 208681 qps cpu 2% 内存150m
总结
未完待续