Java:手动展开的循环仍然比原始循环快。为什么?
考虑长度为2的数组上的以下两段代码:Java:手动展开的循环仍然比原始循环快。为什么?,java,performance,optimization,jit,Java,Performance,Optimization,Jit,考虑长度为2的数组上的以下两段代码: boolean isOK(int i) { for (int j = 0; j < filters.length; ++j) { if (!filters[j].isOK(i)) { return false; } } return true; } 我认为这两件作品在充分预热后的表现应该是相似的 我已经使用JMH micro benchmarking framework(
boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
我认为这两件作品在充分预热后的表现应该是相似的我已经使用JMH micro benchmarking framework(如所述)对此进行了检查,并发现第二个代码片段的速度快了10%以上 问题:为什么Java没有使用基本循环展开技术优化我的第一个代码段?
我特别想了解以下几点:
return(filters.length)==2?新过滤器链2(过滤器):新过滤器链1(过滤器)
。JITC也可以这样做吗?如果不能,为什么更新:得到的答案是JITC只在类级别上工作。好的,明白了
基准运行详细信息:
- 在最新版本的Java8OpenJDK和OracleHotSpot上试用,结果类似
- 使用的Java标志:-Xmx4g-Xms4g-server-Xbatch-XX:CICompilerCount=2(在没有特别标志的情况下也得到了类似的结果)
- 顺便说一下,如果我只是在一个循环中运行它数十亿次(而不是通过JMH),我会得到类似的运行时间比率,也就是说,第二个代码段总是明显更快
LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44.202±0.224纳秒/升
LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38.347 ±0.063纳秒/升 (第一行对应于第一个代码段,第二行对应于第二个代码段 完整的基准代码:
public class LoopUnrollingBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkData {
public Filter[] filters;
@Param({"0", "1"})
public int filterIndex;
public int num;
@Setup(Level.Invocation) //similar ratio with Level.TRIAL
public void setUp() {
filters = new Filter[]{new FilterChain1(), new FilterChain2()};
num = new Random().nextInt();
}
}
@Benchmark
@Fork(warmups = 5, value = 20)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int runBenchmark(BenchmarkData data) {
Filter filter = data.filters[data.filterIndex];
int sum = 0;
int num = data.num;
if (filter.isOK(num)) {
++sum;
}
if (filter.isOK(num + 1)) {
++sum;
}
if (filter.isOK(num - 1)) {
++sum;
}
if (filter.isOK(num * 2)) {
++sum;
}
if (filter.isOK(num * 3)) {
++sum;
}
if (filter.isOK(num * 5)) {
++sum;
}
return sum;
}
interface Filter {
boolean isOK(int i);
}
static class Filter1 implements Filter {
@Override
public boolean isOK(int i) {
return i % 3 == 1;
}
}
static class Filter2 implements Filter {
@Override
public boolean isOK(int i) {
return i % 7 == 3;
}
}
static class FilterChain1 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
}
static class FilterChain2 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
}
private static Filter[] createLeafFilters() {
Filter[] filters = new Filter[2];
filters[0] = new Filter1();
filters[1] = new Filter2();
return filters;
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
public类LoopUnrollingBenchmark{
@国家(范围、基准)
公共静态类基准数据{
公共过滤器[]过滤器;
@参数({“0”,“1”})
公共int过滤器索引;
公共整数;
@Setup(Level.Invocation)//与Level.TRIAL的比率相似
公共作废设置(){
filters=newfilter[]{newfilterchain1(),newfilterchain2()};
num=新随机数().nextInt();
}
}
@基准
@叉子(预热=5,数值=20)
@基准模式(模式平均时间)
@输出时间单位(时间单位纳秒)
public int runBenchmark(BenchmarkData数据){
Filter Filter=data.filters[data.filterIndex];
整数和=0;
int num=data.num;
if(filter.isOK(num)){
++总和;
}
if(filter.isOK(num+1)){
++总和;
}
if(filter.isOK(num-1)){
++总和;
}
if(filter.isOK(num*2)){
++总和;
}
if(filter.isOK(num*3)){
++总和;
}
if(filter.isOK(num*5)){
++总和;
}
回报金额;
}
接口滤波器{
布尔isOK(inti);
}
静态类过滤器1实现过滤器{
@凌驾
公共布尔isOK(int i){
返回i%3==1;
}
}
静态类过滤器2实现过滤器{
@凌驾
公共布尔isOK(int i){
返回i%7==3;
}
}
静态类FilterChain1实现过滤器{
最终过滤器[]过滤器=createLeafFilters();
@凌驾
公共布尔isOK(int i){
对于(int j=0;j
所显示的循环可能属于“未计数”循环类别,这些循环的迭代计数既不能在编译时确定,也不能在运行时确定。这不仅是因为关于数组大小的@Andreas参数,还因为随机条件的中断
(当我写这篇文章时,这曾经是你的基准测试)
最先进的编译器不会太过激进
优化它们,因为展开未计数的循环通常需要
同时复制循环的退出条件,这样只会改进
如果后续编译器优化可以
优化展开的代码。有关详细信息,请参阅本文,他们在哪里提出如何展开这些内容的建议
由此可知,您的假设并不认为您进行了某种“手动展开”你认为这是一个基本的循环展开技术,把一个有条件中断的数组转换成一个<代码> & & <代码>链式布尔表达式。我会认为这是一个非常特殊的情况,会惊奇地发现热点优化器一下子就做了一个复杂的重构。
public class LoopUnrollingBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkData {
public Filter[] filters;
@Param({"0", "1"})
public int filterIndex;
public int num;
@Setup(Level.Invocation) //similar ratio with Level.TRIAL
public void setUp() {
filters = new Filter[]{new FilterChain1(), new FilterChain2()};
num = new Random().nextInt();
}
}
@Benchmark
@Fork(warmups = 5, value = 20)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int runBenchmark(BenchmarkData data) {
Filter filter = data.filters[data.filterIndex];
int sum = 0;
int num = data.num;
if (filter.isOK(num)) {
++sum;
}
if (filter.isOK(num + 1)) {
++sum;
}
if (filter.isOK(num - 1)) {
++sum;
}
if (filter.isOK(num * 2)) {
++sum;
}
if (filter.isOK(num * 3)) {
++sum;
}
if (filter.isOK(num * 5)) {
++sum;
}
return sum;
}
interface Filter {
boolean isOK(int i);
}
static class Filter1 implements Filter {
@Override
public boolean isOK(int i) {
return i % 3 == 1;
}
}
static class Filter2 implements Filter {
@Override
public boolean isOK(int i) {
return i % 7 == 3;
}
}
static class FilterChain1 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
}
static class FilterChain2 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
}
private static Filter[] createLeafFilters() {
Filter[] filters = new Filter[2];
filters[0] = new Filter1();
filters[1] = new Filter2();
return filters;
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
if (! filters[0].isOK(i))
{
return false;
}
if(! filters[1].isOK(i))
{
return false;
}
return true;
if (!cl->has_exact_trip_count()) {
// Trip count is not exact.
return false;
}
// Don't unroll if the next round of unrolling would push us
// over the expected trip count of the loop. One is subtracted
// from the expected trip count because the pre-loop normally
// executes 1 iteration.
if (UnrollLimitForProfileCheck > 0 &&
cl->profile_trip_cnt() != COUNT_UNKNOWN &&
future_unroll_ct > UnrollLimitForProfileCheck &&
(float)future_unroll_ct > cl->profile_trip_cnt() - 1.0) {
return false;
}