数组 [2,2,3,4,9,3]
给一个元素 6
找出数组中两个元素之和等于它,并返回数组坐标。思路:使用map
,key
用来记录arr[i]
的值,value
用来记录对应的下标,使用 containsKey
或者 get
进行逻辑判断 arr[?] = target - arr[??]
@BenchmarkMode(Mode.Throughput) @Warmup(iterations = 3) @Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class TestFindArrayIndex {
@Benchmark public int[] findIndexOfArray1() { int[] arr = new int[] {2,2,3,4,9,3}; int target = 6; Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < arr.length; i++) { if (map.containsKey(target - arr[i])) { return new int[] {map.get(target - arr[i]),i}; } map.put(arr[i], i); } return null; }
@Benchmark public int[] findIndexOfArray2() { int[] arr = new int[] {2,2,3,4,9,3}; int target = 6; Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < arr.length; i++) { if (map.get(arr[i]) != null) { return new int[]{map.get(arr[i]), i}; } map.put(target - arr[i], i); } return null; }
public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(TestFindArrayIndex.class.getSimpleName()) .output("Benchmark.log") .forks(1) .build(); new Runner(opt).run(); } }
|
给出了两个性能差不多的方案,使用 JMH
性能测试,更多例子参考 ,影响基准测试的因素比较多,包括代码预热、编译器动态优化、资源回收(GC)、文件缓存、电源、其他程序,JVM的VM选项等等,详细参考
引入组件
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.20</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.20</version> </dependency>
|
注解介绍
@BenchmarkMode 基准测试类型
public enum Mode { Throughput("thrpt", "Throughput, ops/time"), AverageTime("avgt", "Average time, time/op"), SampleTime("sample", "Sampling time"), SingleShotTime("ss", "Single shot invocation time"), All("all", "All benchmark modes"); }
|
Throughput
: 整体吞吐量,如:1秒内可以执行多少次调用”。
AverageTime
: 调用的平均时间,如:每次调用平均耗时xxx
毫秒。
SampleTime
: 随机取样,最后输出取样结果的分布,如:99%
的调用在xxx
毫秒以内,99.99%
的调用在xxx
毫秒以内
SingleShotTime
: 以上模式都是默认一次 iteration
是 1s
,唯有 SingleShotTime
是只运行一次。往往同时把 warmup
次数设为0
,用于测试冷启动时的性能。
@Warmup 预热
进行基准测试前需要进行预热。一般我们前几次进行程序测试的时候都会比较慢, 所以要让程序进行几轮预热,保证测试的准确性。其中的参数iterations也就非常好理解了,就是预热轮数。
为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface Warmup { int BLANK_ITERATIONS = -1; int BLANK_TIME = -1; int BLANK_BATCHSIZE = -1;
int iterations() default -1;
int time() default -1;
TimeUnit timeUnit() default TimeUnit.SECONDS;
int batchSize() default -1; }
|
@Measurement 度量
@Inherited @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Measurement {
int BLANK_ITERATIONS = -1; int BLANK_TIME = -1; int BLANK_BATCHSIZE = -1;
int iterations() default BLANK_ITERATIONS;
int time() default BLANK_TIME;
TimeUnit timeUnit() default TimeUnit.SECONDS;
int batchSize() default BLANK_BATCHSIZE; }
|
@OutputTimeUnit 基准测试结果的时间类型
@Benchmark 方法级注解,表示该方法是需要进行 benchmark 的对象
这两种方案的性能对比
Benchmark Mode Cnt Score Error Units TestFindArrayIndex.findIndexOfArray1 thrpt 10 16353.697 ± 141.580 ops/ms TestFindArrayIndex.findIndexOfArray2 thrpt 10 15299.888 ± 675.373 ops/ms
|
参考文章