Java Micro-Benchmarking:如何编写正确的基准

几个月前,我写了一篇文章比较循环的短索引的性能 。 我问自己关于使用短裤作为循环迭代次数很少的循环的性能。

在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的两篇非常有趣的文章:

参考: 如何通过 @Blog(“ Baptiste Wicht”)的 JCG合作伙伴 Baptiste Wicht 编写正确的基准

相关文章 :

翻译自: https://www.javacodegeeks.com/2011/09/java-micro-benchmarking-how-to-write.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值