赞
踩
Java Microbenchmark Harness
,是Java
用来做基准测试的工具,测试结果可信度高,用于测试某个方法的性能。13年首发,由JIT开发人员开发,后归于OpenJDK。
官网:OpenJDK: jmh
官方样例: code-tools/jmh: 2be2df7dbaf8 /jmh-samples/src/main/java/org/openjdk/jmh/samples/
需要准确的效果,建议是用命令行的方式,并独一代码单独打包进行测试。
如果想边开发边测试(我是懒得重弄项目了),可以使用JMH的插件。
1,idea安装JMH插件 idea18 版本,装的这个
2,MAVEN
- <!--jmh-->
- <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
- <dependency>
- <groupId>org.openjdk.jmh</groupId>
- <artifactId>jmh-core</artifactId>
- <version>1.21</version>
- </dependency>
-
- <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
- <dependency>
- <groupId>org.openjdk.jmh</groupId>
- <artifactId>jmh-generator-annprocess</artifactId>
- <version>1.21</version>
- <scope>test</scope>
- </dependency>
3,由于用到了注解,打开运行程序注解配置
compiler -> Annotation Processors -> Enable Annotation Processing
5,测试类
- package com.example.demo.jmh;
-
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Random;
-
- public class PS {
-
- static List<Integer> nums = new ArrayList<>();
- static {
- Random r = new Random();
- for (int i = 0; i < 10000; i++) nums.add(1000000 + r.nextInt(1000000));
- }
-
- static void foreach() {
- nums.forEach(v->isPrime(v));
- }
-
- static void parallel() {
- nums.parallelStream().forEach(PS::isPrime);
- }
-
- static boolean isPrime(int num) {
- for(int i=2; i<=num/2; i++) {
- if(num % i == 0) return false;
- }
- return true;
- }
- }
6,单元测试
- package com.example.demo.jmh;
-
- import org.openjdk.jmh.annotations.Benchmark;
-
- public class PSTest {
- @Benchmark
- public void testForEach() {
- PS.foreach();
- }
- }
7,运行测试类,如果遇到下面的错误:
- ERROR: org.openjdk.jmh.runner.RunnerException: ERROR: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock): C:\WINDOWS\jmh.lock (拒绝访问。), exiting. Use -Djmh.ignoreLock=true to forcefully continue.
- at org.openjdk.jmh.runner.Runner.run(Runner.java:216)
- at org.openjdk.jmh.Main.main(Main.java:71)
这个错误是因为JMH运行需要访问系统的TMP目录,解决办法是:
打开RunConfiguration -> Environment Variables -> include system environment viables
8,阅读测试报告
"C:\Program Files\Java\jdk1.8.0_171\bin\java.exe" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\JetBrains\idea18\work\learnRecords\target\test-classes;D:\Program Files\JetBrains\idea18\work\learnRecords\target\classes;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-starter\2.5.3\spring-boot-starter-2.5.3.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot\2.5.3\spring-boot-2.5.3.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-context\5.3.9\spring-context-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-aop\5.3.9\spring-aop-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-beans\5.3.9\spring-beans-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-expression\5.3.9\spring-expression-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.5.3\spring-boot-autoconfigure-2.5.3.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.5.3\spring-boot-starter-logging-2.5.3.jar;C:\Users\jinyh\.m2\repository\ch\qos\logback\logback-classic\1.2.4\logback-classic-1.2.4.jar;C:\Users\jinyh\.m2\repository\ch\qos\logback\logback-core\1.2.4\logback-core-1.2.4.jar;C:\Users\jinyh\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;C:\Users\jinyh\.m2\repository\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;C:\Users\jinyh\.m2\repository\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;C:\Users\jinyh\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-core\5.3.9\spring-core-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-jcl\5.3.9\spring-jcl-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-starter-test\2.5.3\spring-boot-starter-test-2.5.3.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-test\2.5.3\spring-boot-test-2.5.3.jar;C:\Users\jinyh\.m2\repository\org\springframework\boot\spring-boot-test-autoconfigure\2.5.3\spring-boot-test-autoconfigure-2.5.3.jar;C:\Users\jinyh\.m2\repository\com\jayway\jsonpath\json-path\2.5.0\json-path-2.5.0.jar;C:\Users\jinyh\.m2\repository\net\minidev\json-smart\2.4.7\json-smart-2.4.7.jar;C:\Users\jinyh\.m2\repository\net\minidev\accessors-smart\2.4.7\accessors-smart-2.4.7.jar;C:\Users\jinyh\.m2\repository\org\ow2\asm\asm\9.1\asm-9.1.jar;C:\Users\jinyh\.m2\repository\jakarta\xml\bind\jakarta.xml.bind-api\2.3.3\jakarta.xml.bind-api-2.3.3.jar;C:\Users\jinyh\.m2\repository\jakarta\activation\jakarta.activation-api\1.2.2\jakarta.activation-api-1.2.2.jar;C:\Users\jinyh\.m2\repository\org\assertj\assertj-core\3.19.0\assertj-core-3.19.0.jar;C:\Users\jinyh\.m2\repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;C:\Users\jinyh\.m2\repository\org\junit\jupiter\junit-jupiter\5.7.2\junit-jupiter-5.7.2.jar;C:\Users\jinyh\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.7.2\junit-jupiter-api-5.7.2.jar;C:\Users\jinyh\.m2\repository\org\apiguardian\apiguardian-api\1.1.0\apiguardian-api-1.1.0.jar;C:\Users\jinyh\.m2\repository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;C:\Users\jinyh\.m2\repository\org\junit\platform\junit-platform-commons\1.7.2\junit-platform-commons-1.7.2.jar;C:\Users\jinyh\.m2\repository\org\junit\jupiter\junit-jupiter-params\5.7.2\junit-jupiter-params-5.7.2.jar;C:\Users\jinyh\.m2\repository\org\junit\jupiter\junit-jupiter-engine\5.7.2\junit-jupiter-engine-5.7.2.jar;C:\Users\jinyh\.m2\repository\org\junit\platform\junit-platform-engine\1.7.2\junit-platform-engine-1.7.2.jar;C:\Users\jinyh\.m2\repository\org\mockito\mockito-core\3.9.0\mockito-core-3.9.0.jar;C:\Users\jinyh\.m2\repository\net\bytebuddy\byte-buddy\1.10.22\byte-buddy-1.10.22.jar;C:\Users\jinyh\.m2\repository\net\bytebuddy\byte-buddy-agent\1.10.22\byte-buddy-agent-1.10.22.jar;C:\Users\jinyh\.m2\repository\org\objenesis\objenesis\3.2\objenesis-3.2.jar;C:\Users\jinyh\.m2\repository\org\mockito\mockito-junit-jupiter\3.9.0\mockito-junit-jupiter-3.9.0.jar;C:\Users\jinyh\.m2\repository\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;C:\Users\jinyh\.m2\repository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;C:\Users\jinyh\.m2\repository\org\springframework\spring-test\5.3.9\spring-test-5.3.9.jar;C:\Users\jinyh\.m2\repository\org\xmlunit\xmlunit-core\2.8.2\xmlunit-core-2.8.2.jar;C:\Users\jinyh\.m2\repository\net\sourceforge\jexcelapi\jxl\2.6.12\jxl-2.6.12.jar;C:\Users\jinyh\.m2\repository\log4j\log4j\1.2.14\log4j-1.2.14.jar;C:\Users\jinyh\.m2\repository\org\apache\zookeeper\zookeeper\3.4.6\zookeeper-3.4.6.jar;C:\Users\jinyh\.m2\repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;C:\Users\jinyh\.m2\repository\org\slf4j\slf4j-log4j12\1.7.32\slf4j-log4j12-1.7.32.jar;C:\Users\jinyh\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\jinyh\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\jinyh\.m2\repository\org\hamcrest\hamcrest-core\2.2\hamcrest-core-2.2.jar;C:\Users\jinyh\.m2\repository\io\netty\netty\3.7.0.Final\netty-3.7.0.Final.jar;C:\Users\jinyh\.m2\repository\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar;C:\Users\jinyh\.m2\repository\org\openjdk\jmh\jmh-core\1.21\jmh-core-1.21.jar;C:\Users\jinyh\.m2\repository\net\sf\jopt-simple\jopt-simple\4.6\jopt-simple-4.6.jar;C:\Users\jinyh\.m2\repository\org\apache\commons\commons-math3\3.2\commons-math3-3.2.jar;C:\Users\jinyh\.m2\repository\org\openjdk\jmh\jmh-generator-annprocess\1.21\jmh-generator-annprocess-1.21.jar" org.openjdk.jmh.Main com.example.demo.jmh.PSTest.testForEach # JMH version: 1.21 # VM version: JDK 1.8.0_171, Java HotSpot(TM) 64-Bit Server VM, 25.171-b11 # VM invoker: C:\Program Files\Java\jdk1.8.0_171\jre\bin\java.exe # VM options: -Dfile.encoding=UTF-8 # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: com.example.demo.jmh.PSTest.testForEach # Run progress: 0.00% complete, ETA 00:08:20 # Fork: 1 of 5 # Warmup Iteration 1: 0.754 ops/s # Warmup Iteration 2: 0.751 ops/s # Warmup Iteration 3: 0.759 ops/s # Warmup Iteration 4: 0.762 ops/s # Warmup Iteration 5: 0.756 ops/s Iteration 1: 0.754 ops/s Iteration 2: 0.758 ops/s Iteration 3: 0.760 ops/s Iteration 4: 0.757 ops/s Iteration 5: 0.751 ops/s # Run progress: 20.00% complete, ETA 00:07:06 # Fork: 2 of 5 # Warmup Iteration 1: 0.761 ops/s # Warmup Iteration 2: 0.773 ops/s # Warmup Iteration 3: 0.764 ops/s # Warmup Iteration 4: 0.768 ops/s # Warmup Iteration 5: 0.772 ops/s Iteration 1: 0.775 ops/s Iteration 2: 0.773 ops/s Iteration 3: 0.760 ops/s Iteration 4: 0.737 ops/s Iteration 5: 0.748 ops/s # Run progress: 40.00% complete, ETA 00:05:18 # Fork: 3 of 5 # Warmup Iteration 1: 0.765 ops/s # Warmup Iteration 2: 0.705 ops/s # Warmup Iteration 3: 0.723 ops/s # Warmup Iteration 4: 0.763 ops/s # Warmup Iteration 5: 0.753 ops/s Iteration 1: 0.751 ops/s Iteration 2: 0.752 ops/s Iteration 3: 0.759 ops/s Iteration 4: 0.753 ops/s Iteration 5: 0.761 ops/s # Run progress: 60.00% complete, ETA 00:03:33 # Fork: 4 of 5 # Warmup Iteration 1: 0.748 ops/s # Warmup Iteration 2: 0.743 ops/s # Warmup Iteration 3: 0.746 ops/s # Warmup Iteration 4: 0.742 ops/s # Warmup Iteration 5: 0.739 ops/s Iteration 1: 0.744 ops/s Iteration 2: 0.734 ops/s Iteration 3: 0.739 ops/s Iteration 4: 0.729 ops/s Iteration 5: 0.749 ops/s # Run progress: 80.00% complete, ETA 00:01:47 # Fork: 5 of 5 # Warmup Iteration 1: 0.757 ops/s # Warmup Iteration 2: 0.737 ops/s # Warmup Iteration 3: 0.726 ops/s # Warmup Iteration 4: 0.730 ops/s # Warmup Iteration 5: 0.717 ops/s Iteration 1: 0.720 ops/s Iteration 2: 0.721 ops/s Iteration 3: 0.737 ops/s Iteration 4: 0.747 ops/s Iteration 5: 0.714 ops/s Result "com.example.demo.jmh.PSTest.testForEach": 0.747 ±(99.9%) 0.012 ops/s [Average] (min, avg, max) = (0.714, 0.747, 0.775), stdev = 0.016 CI (99.9%): [0.736, 0.759] (assumes normal distribution) # Run complete. Total time: 00:08:58 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units PSTest.testForEach thrpt 25 0.747 ± 0.012 ops/s Process finished with exit code 0
Warmup 预热,由于JVM中对于特定代码会存在优化(本地化),预热对于测试结果很重要
Mesurement 总共执行多少次测试
Timeout
Threads 线程数,由fork指定
Benchmark mode 基准测试的模式
Benchmark 测试哪一段代码
源码:https://github.com/LMAX-Exchange/disruptor
GettingStarted: Getting Started · LMAX-Exchange/disruptor Wiki · GitHub
api: http://lmax-exchange.github.io/disruptor/docs/index.html
maven: https://mvnrepository.com/artifact/com.lmax/disruptor
一个线程中每秒处理600万订单,是金融机构开发的用于交易的。
2011年获得Duke奖项
单机速度最快的MQ
性能极高,无锁CAS,单机支持高并发
内存中用于存储的高效队列。
对比ConcurrentLinkedQueue : 链表实现
JDK中没有ConcurrentArrayQueue ,是因为数组是固定长度的,那么每次扩展都需要复制+1,效率低
Disruptor是数组实现的
数组是实现的ConcurrentArrayQueue 且头尾相连,
为什么效率比链表快,第一,链表没有数组遍历快;第二,链表需要维护头尾两个指针,数组只需维护一个sequence序列(下个元素的位置);
怎么计算加在环形数组的位置? 添加元素的第几个元素 / 数组容量 求余
无锁,高并发,使用环形Buffer,直接覆盖(不用清除)旧的数据,降低GC频率
实现了基于事件的生产者消费者模式(观察者模式)
环形队列
RingBuffer的序号,指向下一个可用的元素
采用数组实现,没有首尾指针
对比ConcurrentLinkedQueue,用数组实现的速度更快
假如长度为8,当添加到第12个元素的时候在哪个序号上呢?用12%8决定
当Buffer被填满的时候到底是覆盖还是等待,由Producer决定
长度设为2的n次幂,利于二进制计算,例如:12%8 = 12 & (8 - 1) pos = num & (size -1)
01:14“09
定义Event - 队列中需要处理的元素
定义Event工厂,用于填充队列
这里牵扯到效率问题:disruptor初始化的时候,会调用Event工厂,对ringBuffer进行内存的提前分配
GC产频率会降低
定义EventHandler(消费者),处理容器中的元素
long sequence = ringBuffer.next(); // Grab the next sequence try { LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor // for the sequence event.set(8888L); // Fill with data } finally { ringBuffer.publish(sequence); }
//=============================================================== EventTranslator<LongEvent> translator1 = new EventTranslator<LongEvent>() { @Override public void translateTo(LongEvent event, long sequence) { event.set(8888L); } }; ringBuffer.publishEvent(translator1); //=============================================================== EventTranslatorOneArg<LongEvent, Long> translator2 = new EventTranslatorOneArg<LongEvent, Long>() { @Override public void translateTo(LongEvent event, long sequence, Long l) { event.set(l); } }; ringBuffer.publishEvent(translator2, 7777L); //=============================================================== EventTranslatorTwoArg<LongEvent, Long, Long> translator3 = new EventTranslatorTwoArg<LongEvent, Long, Long>() { @Override public void translateTo(LongEvent event, long sequence, Long l1, Long l2) { event.set(l1 + l2); } }; ringBuffer.publishEvent(translator3, 10000L, 10000L); //=============================================================== EventTranslatorThreeArg<LongEvent, Long, Long, Long> translator4 = new EventTranslatorThreeArg<LongEvent, Long, Long, Long>() { @Override public void translateTo(LongEvent event, long sequence, Long l1, Long l2, Long l3) { event.set(l1 + l2 + l3); } }; ringBuffer.publishEvent(translator4, 10000L, 10000L, 1000L); //=============================================================== EventTranslatorVararg<LongEvent> translator5 = new EventTranslatorVararg<LongEvent>() { @Override public void translateTo(LongEvent event, long sequence, Object... objects) { long result = 0; for(Object o : objects) { long l = (Long)o; result += l; } event.set(result); } }; ringBuffer.publishEvent(translator5, 10000L, 10000L, 10000L, 10000L);
package com.mashibing.disruptor; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; public class Main03 { public static void main(String[] args) throws Exception { // Specify the size of the ring buffer, must be power of 2. int bufferSize = 1024; // Construct the Disruptor Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE); // Connect the handler disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event)); // Start the Disruptor, starts all threads running disruptor.start(); // Get the ring buffer from the Disruptor to be used for publishing. RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); ringBuffer.publishEvent((event, sequence) -> event.set(10000L)); System.in.read(); } }
ProducerType有两种模式 Producer.MULTI和Producer.SINGLE
默认是MULTI,表示在多线程模式下产生sequence
如果确认是单线程生产者,那么可以指定SINGLE,效率会提升
如果是多个生产者(多线程),但模式指定为SINGLE,会出什么问题呢?
如果来不及消费,会被覆盖怎么办?这就引出了等待策略
1,(常用)BlockingWaitStrategy:通过线程阻塞的方式,等待生产者唤醒,被唤醒后,再循环检查依赖的sequence是否已经消费。
2,BusySpinWaitStrategy:线程一直自旋等待,可能比较耗cpu
3,LiteBlockingWaitStrategy:线程阻塞等待生产者唤醒,与BlockingWaitStrategy相比,区别在signalNeeded.getAndSet,如果两个线程同时访问一个访问waitfor,一个访问signalAll时,可以减少lock加锁次数.
4,LiteTimeoutBlockingWaitStrategy:与LiteBlockingWaitStrategy相比,设置了阻塞时间,超过时间后抛异常。
5,PhasedBackoffWaitStrategy:根据时间参数和传入的等待策略来决定使用哪种等待策略
6,TimeoutBlockingWaitStrategy:相对于BlockingWaitStrategy来说,设置了等待时间,超过后抛异常
7,(常用)YieldingWaitStrategy:尝试100次,然后Thread.yield()让出cpu
8,(常用)SleepingWaitStrategy : sleep
默认:disruptor.setDefaultExceptionHandler()
覆盖:disruptor.handleExceptionFor().with()
- <!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
- <dependency>
- <groupId>com.lmax</groupId>
- <artifactId>disruptor</artifactId>
- <version>3.4.2</version>
- </dependency>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。