健壮的(robust)Java benchmarking 阅读笔记
理解Java代码编写的benchmarking的陷阱(pitfalls)
采样与计时
- 对目标方法多次后计算平均耗时。
- 使用System.nanoTime,而不是System.currentTimeMillis(毫秒 由于一些操作系统差异,导致其精度有问题)。
代码预热
大多数JVM实现有一个复杂难懂的性能生命周期,一般来讲,初始的性能通常是慢的,然后他不断提升,直至到一个稳定状态。
类加载
JVM针对类加载的典型做法是在其第一次使用时。所以第一次执行任务的时间包括了类加载的时间,这次包括磁盘io、解析、验证等等。
可以通过ClassLoadingMXbean的getTotalLoadedClassCount和getUnloadedClassCount方法在benchmark执行的前后确定类的加载与卸载。
混合模式
先执行task一次加载所有的class。
执行task足够多次,以确保性能达到稳定状态。
继续执行task一些次数,计算总耗时并除以次数后得到结果。
动态优化
逆优化
JVM有时会停止使用编译的方式退化到解释模式。有两个例子如下:
- class loading会使一些单一的调用转换过期作废。
- 一些不正常的路径被执行到,比如异常分支,如果执行了很多次则会触发重新编译。
OSR问题
OSR(On-stack replacement)可以用在单一方法调用的内部的多次循环上,但是OSR有时不能做循环提升,数组范围检查消除和循环展开,进而影响性能数据。
唯一的解决办法是识别出它能发生在哪里,且重新结构化你的代码。一般的做法是,将关键的代码循环放入到单独的方法中。
文中的case,阻止OSR后,性能提升一倍。
但是OSR也不是那么脆弱,不要过于对此焦虑(anxious),除非你确定你的代码是因OSR造成了issue。
DCE问题
DCE(Dead-code elimination)的典型例子有个是jjavac在编译期静态的进行消除。动态编译器也可以做DCE,但是我们暂时没有一个好的描述关于怎样的代码就算是dead code。虽说不可达代码是显而易见的一种,但是JVM有更多的激进的DCE策略。比如文中的case:
- 计算result
- 最后输出result
对于步骤2是否执行,会极大的影响程序的性能,如果不输出result,JVM会认为是DCE,则不再对result进行计算,那么将得到一个错误的性能数据。所以你如果要得到正确的计算result的性能数据,那么需要在最后将result输出(或者其他手段阻止DCE发生)。
比较阻止OSR和阻止DCE:
- 未阻止OSR,最多导致一个不精确的结果。
- 未阻止DCE,将导致一个完全错误的结果。
资源回收
主要讲了GC/OF对测试的影响,并给出了错误的统计GC/OF影响的方式。
使用System.gc时可能会得到一个错误的失真的结果,注意jvm的参数-XX:+ExplicitGCInvokesConcurrent -XX:+DisableExplicitGC。
其他
系统缓存、电源、其他程序、JVM选项对benchmark的影响
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!