首先为自己没有经过严格测试得出的错误结论感到抱歉,原博文,测试完感觉自己发现了一个新bug,后来思前想后觉得不应该是这样的,如果效率差的这么多,jdk的开发人员会不去优化它吗,但是怎么重复测试任然得到一样的结果,非常疑惑。
我觉得应该是测试方法出问题了,可是怎么也想不到原因,后来了解到jmh,深入研究了一番,觉得jmh的测试值得借鉴,jmh在测试的时候都会先经过预热几遍要测试的代码,示例如下:
public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(JMHSample_02_BenchmarkModes.class.getSimpleName()) .warmupIterations(5) .measurementIterations(5) .forks(1) .build(); new Runner(opt).run(); }
我觉得这是有必要的,而我的测试恰恰缺少了这个步骤,随后我在测试中应用了预热处理(均是3次预热,5次运行取平均值),得出的以下结论。
首先是上个测试中被冤枉的java8的foreach循环,测试代码:
package yiwangzhibujian.jmh.test; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @State(Scope.Thread) @BenchmarkMode(Mode.SingleShotTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class J8Loop { public static Map<Integer,List<Dog>> dogMap=new HashMap<>(); static { dogMap.put(10, getDogs(10)); dogMap.put(100, getDogs(100)); dogMap.put(1000, getDogs(1000)); dogMap.put(10000, getDogs(10000)); dogMap.put(100000, getDogs(100000)); dogMap.put(1000000, getDogs(1000000)); } private static List<Dog> getDogs(int num) { List<Dog> dogs=new ArrayList<>(); for(int i=0;i<num;i++){ dogs.add(new Dog(i,"dog"+i)); } return dogs; } private void loop(List<Dog> list) { list.forEach(dog->{ dog.hashCode(); }); } @Benchmark @OperationsPerInvocation(10) public void measureWrong_10() { List<Dog> list = dogMap.get(10); loop(list); } @Benchmark @OperationsPerInvocation(100) public void measureWrong_100() { List<Dog> list = dogMap.get(100); loop(list); } @Benchmark @OperationsPerInvocation(1000) public void measureWrong_1000() { List<Dog> list = dogMap.get(1000); loop(list); } @Benchmark @OperationsPerInvocation(10000) public void measureWrong_10000() { List<Dog> list = dogMap.get(10000); loop(list); } @Benchmark @OperationsPerInvocation(100000) public void measureWrong_100000() { List<Dog> list = dogMap.get(100000); loop(list); } @Benchmark @OperationsPerInvocation(1000000) public void measureWrong_1000000() { List<Dog> list = dogMap.get(1000000); loop(list); } /* * ============================== HOW TO RUN THIS TEST: * ==================================== * * You might notice the larger the repetitions count, the lower the * "perceived" cost of the operation being measured. Up to the point we do * each addition with 1/20 ns, well beyond what hardware can actually do. * * This happens because the loop is heavily unrolled/pipelined, and the * operation to be measured is hoisted from the loop. Morale: don't overuse * loops, rely on JMH to get the measurement right. * * You can run this test: * * a) Via the command line: $ mvn clean install $ java -jar * target/benchmarks.jar JMHSample_11 -wi 5 -i 5 -f 1 (we requested 5 * warmup/measurement iterations, single fork) * * b) Via the Java API: (see the JMH homepage for possible caveats when * running from IDE: http://openjdk.java.net/projects/code-tools/jmh/) */ public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(J8Loop.class.getSimpleName()) .warmupIterations(2) .measurementIterations(2).forks(1).build(); new Runner(opt).run(); } } class Dog{ private int age; private String name; public Dog(int age, String name) { super(); this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Dog [age=" + age + ", name=" + name + "]"; } }