在Java语言中,所有对整数的操作都是int进行的。 因此,如果我们使用short作为循环索引,则在每次迭代时都将进行类型转换,这实际上比对int的简单影响要重。
我编写该代码来实现我的目标:
package com.wicht.old;public class TestShortInt {public static void main(String[] args){long startTime = System.nanoTime();int resultInt = 0;for (int i = 0; i < 100000; i++){for (int j = 0; j < 32760; j++){resultInt += i * j;}}System.out.println("Temp pour int : " + (System.nanoTime() - startTime) / 1000000 + " ms");startTime = System.nanoTime();int resultShort = 0;for (int k = 0; k < 100000; k++){for (short f = 0; f < 32760; f++){resultShort += k * f;}}System.out.println("Temp pour short : " + (System.nanoTime() - startTime) / 1000000 + " ms");System.out.println(resultInt);System.out.println(resultShort);}
}
结果我发现short比int慢了两倍,直到一周前我才确信这些结果。
这时,一位读者(Jean)批评了我的测试结果,并给了我有关微基准测试的几篇文章的链接。 我阅读了这些文章,并了解了为什么我的结果不正确。
实际上,我的测试没有注意可能改变测试结果的几件事:
- JVM预热 :由于有几个参数,代码通常通常会很慢,并且随着执行时间的增长直到达到稳态,代码会变得越来越快。
- 类加载 :第一次启动基准测试时,必须加载所有使用的类,从而增加了执行时间。
- 即时编译器 :当JVM识别出代码的重要部分时
- 垃圾收集器 :在基准测试期间可能会发生垃圾收集,并且时间会大大增加。
由于所有这些因素,第一次运行(可能需要运行10秒)比其他运行速度慢,并且会使基准完全错误。
那么,我们如何才能取得良好的基准测试结果呢?
这确实很困难,但是我们可以使用Elliptic Group的软件开发人员Brent Boyer引入的基准框架获得帮助。 该框架照顾了所有先前引入的因素,并制定了良好的基准。
该框架的使用非常简单,您只需创建Benchmark类的新实例,并将其传递给Callable或Runnable即可,然后直接启动测试。 这是在循环索引中测试short和int的示例:
public class ShortIndexesLoop {public static void main(String[] args) {Callable callableInt = new Callable(){public Long call() throws Exception {long result = 0;for (int f = 0; f < 32760; f++){result += 444;}return result;}};Callable callableShort = new Callable(){public Long call() throws Exception {long result = 0;for (short f = 0; f < 32760; f++){result += 444;}return result;}};try {Benchmark intBenchmark = new Benchmark(callableInt);System.out.println("Result with int ");System.out.println(intBenchmark.toString());Benchmark shortBenchmark = new Benchmark(callableShort);System.out.println("Result short ");System.out.println(shortBenchmark.toString());} catch (Exception e) {e.printStackTrace();}}
}
要获得结果,可以使用Benchmark.toString()或Benchmark.toStringFull()获得更多统计信息。 您还可以使用Benchmark.getSd()直接访问某些统计信息(例如标准差),也可以直接使用Benchmark.getStats()获取所有统计信息。
这是前面代码的结果:
结果int first = 807.056 us,平均值= 46.032 us(CI delta:-261.393 ns,+408.932 ns),sd = 230.929 us(CI delta:-68.201 us,+105.262 us)
结果短短优先= 721.912 us,平均值= 48.234 us(CI delta:-198.625 ns,+254.774 ns),sd = 160.196 us(CI delta:-32.764 us,+37.882 us)
如您所见,短版本仅比int慢104.78%。 这表明最初的结果是完全错误的。
这是int版本的完整结果:
动作统计信息:第一个= 807.056 us,平均值= 46.032 us(CI delta:-261.393 ns,+408.932 ns),sd = 230.929 us(CI delta:-68.201 us,+105.262 us)警告:执行时间有极端异常情况,标清值可能不准确---根据块统计信息计算操作统计信息-每个块测量32768个任务执行-用户说任务内部执行m = 1个动作-那么每个块测量的动作数为a = 32768-阻止统计信息:平均值= 1.508 s(CI增量:-8.565毫秒,+ 13.400毫秒),sd = 41.803毫秒(CI增量:-12.346毫秒,+ 19.054毫秒)–用于将阻止统计信息转换为行动统计信息的论坛(均值)假设a / a为1 / s,则sd缩放为1 / sqrt(a))假定操作执行时间为iid — —每个置信区间(CI)被报告为点估计的+-增量或封闭点间隔([x,y])–每个置信区间的置信度为0.95 — —-–执行时间显示在外边的情况–使用箱线图确定 中位数= 1.498 s,interquantileRange = 34.127 ms的算法– 3是极度(偏高):#57 = 1.621 s,#58 = 1.647 s,#59 = 1.688 s –2是温和的(偏高): #55 = 1.570 s,#56 = 1.582 s ———-阻止sd值可能无法反映任务的内在变化–猜测:环境噪声至少解释了所测量sd的55.89418621876822%———- –动作sd值几乎完全相同由离群值填充–根据等值离群值模型,它们至少导致了98.95646276911543%的已测量方差–模型数量:a = 32768.0,muB = 1.5083895562166663,sigmaB = 0.04180264914581472,muA = 4.603239612477619E-5,sigmaA = 2.3092919283255957E- 4,tMin = 0.0,muGMin = 2.3016198062388096E-5,sigmaG = 5.754049515597024E-6,cMax1 = 1252,cMax2 = 322,cMax = 322,cOutMin = 322,varOutMin = 0.0017292260645147487,muG(cOutMin)= 2.3034259031465023E-5, U(cOutMin)= 0.002363416110812895
就像您在使用此框架时可能看到的那样,当您举例说明您存在极端异常值可能使标准偏差完全错误时,它会向您发出一些警告。
您可以在Elliptic Group的网页上下载此框架。 我发现它非常强大且易于使用,并且每次需要进行基准测试时都会使用它。
总而言之,我还必须说,即使您使用那种框架,如果您没有测试代码的正确部分,也可能会导致非常糟糕的基准测试。 这是来自Brent Boyer的两篇非常有趣的文章:
- 健壮的Java基准测试,第1部分:问题
- 强大的Java基准测试,第2部分:统计信息和解决方案
参考: 如何通过 @Blog(“ Baptiste Wicht”)的 JCG合作伙伴 Baptiste Wicht 编写正确的基准 。
- 绩效焦虑–关于绩效不可预测性,度量和基准
- 改善Java应用程序性能的快速技巧
- 如何在Java中获得类似于C的性能
- Java中的低GC:使用原语而不是包装器
- 如何在不到1ms的延迟内完成100K TPS
翻译自: https://www.javacodegeeks.com/2011/09/java-micro-benchmarking-how-to-write.html