Java Hotspot服务器中的多态性成本很高 [英] High cost of polymorphism in Java Hotspot server

查看:83
本文介绍了Java Hotspot服务器中的多态性成本很高的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在Java Hotspot客户端中运行我的计时测试程序时,我得到了一致的行为。
但是,当我在Hotspot服务器上运行它时,我得到了意想不到的结果。
从本质上讲,多态性的成本在某些情况下是高得令人无法接受的,我已经尝试过
来复制以下内容。

When I run my timing test program in Java Hotspot client, I get consistent behavior. However, when I run it in Hotspot server, I get unexpected result. Essentially, the cost of polymorphism is unacceptably high in certain situations that I've tried to duplicate bellow.

这是一个已知问题/ Hotspot服务器的错误,或者我做错了什么?

Is this a known issue/bug with Hotspot server, or am I doing something wrong?

测试程序和时间如下:

Intel i7, Windows 8
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
Mine2: 0.387028831 <--- polymorphic call with expected timing
Trivial: 1.545411765 <--- some more polymorphic calls
Mine: 0.727726371 <--- polymorphic call with unexpected timing. Should be about 0.38
Mine: 0.383132698 <--- direct call with expected timing

随着我添加额外的测试,情况变得更糟。
接近列表末尾的测试时间完全关闭。

The situation gets worse as I add additional tests. Timing of the tests near the end of the list are completely off.

interface canDoIsSquare {
    boolean isSquare(long x);
}

final class Trivial implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        if (x > 0) {
            long t = (long) Math.sqrt(x);
            return t * t == x;
        }
        return x == 0;
    }
    @Override public String toString() {return "Trivial";}
}

final class Mine implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        if (x > 0) {
            while ((x & 3) == 0)
                x >>= 2;
            if ((x & 2) != 0 || (x & 7) == 5)
                return false;
            final long t = (long) Math.sqrt(x);
            return (t * t == x);
        }
        return x == 0;
    }

    @Override public String toString() {return "Mine";}
}

final class Mine2 implements canDoIsSquare {
    @Override final public boolean isSquare(long x) {
        // just duplicated code for this test
        if (x > 0) {
            while ((x & 3) == 0)
                x >>= 2;
            if ((x & 2) != 0 || (x & 7) == 5)
                return false;
            final long t = (long) Math.sqrt(x);
            return (t * t == x);
        }
        return x == 0;
    }
    @Override final public String toString() {return "Mine2";}
}

public class IsSquared {
    static final long init = (long) (Integer.MAX_VALUE / 8)
            * (Integer.MAX_VALUE / 2) + 1L;

    static long test1(final canDoIsSquare fun) {
        long r = init;
        long startTimeNano = System.nanoTime();
        while (!fun.isSquare(r))
            ++r;
        long taskTimeNano = System.nanoTime() - startTimeNano;
        System.out.println(fun + ": " + taskTimeNano / 1e9);
        return r;
    }

    static public void main(String[] args) {
        Mine mine = new Mine();
        Trivial trivial = new Trivial();
        Mine2 mine2 = new Mine2();

        test1(mine2);
        test1(trivial);
        test1(mine);

        long r = init;
        long startTimeNano = System.nanoTime();
        while (!mine.isSquare(r))
            ++r;
        long taskTimeNano = System.nanoTime() - startTimeNano;
        System.out.println(mine + ": " + taskTimeNano / 1e9);
        System.out.println(r);
    }
}


推荐答案

实际上,成本很高,但是你的基准并没有衡量真正相关的东西。 JIT可以优化大部分开销,但你没有给它任何机会。参见例如此处

The cost is high, indeed, but your benchmark doesn't measure anything really relevant. The JIT can optimize away most of the overhead, but you didn't give it any chance. See e.g. here.

在任何情况下,都没有基准热身,并且有在堆栈更换

In any case, there's no benchmark warmup and there's On Stack Replacement.

解释可能是Server Hotspot优化得更好但速度更慢。它假设它有足够的时间并且收集更长的必要统计数据。因此,虽然客户端热点优化了您的程序,但是服务器热点正准备自己生成更好的代码。

The explanation is probably that the Server Hotspot optimizes better but slower. It assumes that it has enough time and collects the necessary stats longer. So while the Client Hotspot optimized your program, the Server Hotspot was preparing itself to produce better code.

其他测试恶化的原因是最初的单形呼叫站点变为双态,然后变形。

The reason for the worsening with additional tests is that the initially monomorphic call site became bimorphic and then megamorphic.

实际上,有可能只调用其中一个方法。如果您想要基准测试,则必须在自己的JVM中运行每个测试。这是一个真正的痛苦,但现有的基准测试框架为您完成。

In reality it's possible that only one of the methods gets called. If you want benchmark this, you have to run each test in its own JVM. This is a real pain, but existing benchmarking frameworks do it for you.

或者您可能想要测量多态情况,但是您需要预热代码所有案件首先。通过这种方式,即使在单个JVM中,您也可以找到哪种方法更快(尽管每个JVM都会因为变形调用开销而减慢。

Or you may want to measure the polymorphic case, but then you need to warm up the code with all cases first. This way you can find out which method is faster even in a single JVM (though each will be slowed down by the megamorphic call overhead.

解释似乎是从单态变为megamorhic。当第一次测试运行时,JVM知道所有类(因为实例已经创建),但乐观地假设只有 Mine2 发生在呼叫站点上。因此它进行了快速检查(翻译为条件分支,始终正确预测,因此非常快),并调用正确的方法。由于后来看到其他两个实例在那里使用,它必须为它们创建一个分支表(分支预测仍然有效,但开销更高)。

The explanation seems to be the change from monomorphic to megamorhic. When the first test was run, the JVM was knew all the classes (as the instances were already created), but was optimistically assuming that only Mine2 occurs on the call site. So it did a quick check (translated as a conditional branch, which was always correctly predicted and thus very fast), and called the proper method. As it later saw the other two instances being used there, it had to create a branch table for them (the branch prediction still works, but the overhead is higher).

什么不清楚:JVM可以将此测试移出循环,从而将其成本降低到几乎为零。我不知道为什么它不会发生。

What's unclear: The JVM can move this test out of the loop and thus reduce it's cost to nearly nothing. I can't tell why it doesn't happen.

这篇关于Java Hotspot服务器中的多态性成本很高的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆