Java性能谜题:包装类比原始类型快?

Java性能谜题:包装类比原始类型快?,java,performance,benchmarking,primitive-types,visitor-pattern,Java,Performance,Benchmarking,Primitive Types,Visitor Pattern,为了实现一些图像分析算法,而不必太过担心数据类型,即不需要太多重复代码,我正在为Java中的基本数组设置访问者模式 在下面的示例中,我定义了两种类型的访问者 一种基本类型,其中访问方法的签名为visitint,int double 泛型类型,其中visit方法的签名为visitint,int Double。 显然,两位访客的操作完全相同。我的想法是尝试衡量装箱/拆箱的成本 这是完整的程序 public class VisitorsBenchmark { public interface

为了实现一些图像分析算法,而不必太过担心数据类型,即不需要太多重复代码,我正在为Java中的基本数组设置访问者模式

在下面的示例中,我定义了两种类型的访问者

一种基本类型,其中访问方法的签名为visitint,int double 泛型类型,其中visit方法的签名为visitint,int Double。 显然,两位访客的操作完全相同。我的想法是尝试衡量装箱/拆箱的成本

这是完整的程序

public class VisitorsBenchmark {
    public interface Array2DGenericVisitor<TYPE, RET> {

        void begin(int width, int height);

        RET end();

        void visit(int x, int y, TYPE value);
    }

    public interface Array2DPrimitiveVisitor<RET> {

        void begin(final int width, final int height);

        RET end();

        void visit(final int x, final int y, final double value);
    }

    public static <RET>
        RET
        accept(final int width,
               final int height,
               final double[] data,
               final Array2DGenericVisitor<Double, RET> visitor) {

        final int size = width * height;
        visitor.begin(width, height);
        for (int i = 0, x = 0, y = 0; i < size; i++) {
            visitor.visit(x, y, data[i]);
            x++;
            if (x == width) {
                x = 0;
                y++;
                if (y == height) {
                    y = 0;
                }
            }
        }
        return visitor.end();
    }

    public static <RET> RET accept(final int width,
                                   final int height,
                                   final double[] data,
                                   final Array2DPrimitiveVisitor<RET> visitor) {

        final int size = width * height;
        visitor.begin(width, height);
        for (int i = 0, x = 0, y = 0; i < size; i++) {
            visitor.visit(x, y, data[i]);
            x++;
            if (x == width) {
                x = 0;
                y++;
                if (y == height) {
                    y = 0;
                }
            }
        }
        return visitor.end();
    }

    private static final Array2DGenericVisitor<Double, double[]> generic;

    private static final Array2DPrimitiveVisitor<double[]> primitive;

    static {
        generic = new Array2DGenericVisitor<Double, double[]>() {
            private double[] sum;

            @Override
            public void begin(final int width, final int height) {

                final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
                sum = new double[length];
            }

            @Override
            public void visit(final int x, final int y, final Double value) {

                final int r = (int) Math.round(Math.sqrt(x * x + y * y));
                sum[r] += value;
            }

            @Override
            public double[] end() {

                return sum;
            }
        };

        primitive = new Array2DPrimitiveVisitor<double[]>() {
            private double[] sum;

            @Override
            public void begin(final int width, final int height) {

                final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
                sum = new double[length];
            }

            @Override
            public void visit(final int x, final int y, final double value) {

                final int r = (int) Math.round(Math.sqrt(x * x + y * y));
                sum[r] += value;
            }

            @Override
            public double[] end() {

                return sum;
            }
        };
    }

    private static final int WIDTH = 300;

    private static final int HEIGHT = 300;

    private static final int NUM_ITERATIONS_PREHEATING = 10000;

    private static final int NUM_ITERATIONS_BENCHMARKING = 10000;

    public static void main(String[] args) {

        final double[] data = new double[WIDTH * HEIGHT];
        for (int i = 0; i < data.length; i++) {
            data[i] = Math.random();
        }

        /*
         * Pre-heating.
         */
        for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
            accept(WIDTH, HEIGHT, data, generic);
        }
        for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
            accept(WIDTH, HEIGHT, data, primitive);
        }

        /*
         * Benchmarking proper.
         */
        double[] sumPrimitive = null;
        double[] sumGeneric = null;

        double aux = System.nanoTime();
        for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
            sumGeneric = accept(WIDTH, HEIGHT, data, generic);
        }
        final double timeGeneric = System.nanoTime() - aux;

        aux = System.nanoTime();
        for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
            sumPrimitive = accept(WIDTH, HEIGHT, data, primitive);
        }
        final double timePrimitive = System.nanoTime() - aux;

        System.out.println("prim = " + timePrimitive);
        System.out.println("generic = " + timeGeneric);
        System.out.println("generic / primitive = "
                           + (timeGeneric / timePrimitive));
    }
}
我知道JIT非常聪明,所以当两位访客都表现得同样出色时,我并不感到惊讶。 更令人惊讶的是,泛型访问者的执行速度似乎略快于原语,这是出乎意料的。我知道基准测试有时会很困难,所以我一定是做错了什么。你能发现错误吗

非常感谢你的帮助!!! 塞巴斯蒂安

[编辑]为了让JIT编译器完成它的工作,我已经更新了代码以说明预热阶段。这不会改变结果,结果始终低于1 0.95-0.98

我知道基准测试有时会很困难,所以我一定是做错了什么。你能发现错误吗

我认为问题在于您的基准测试没有考虑JVM预热。把你的主要方法的主体放在另一个方法中。然后让主方法在循环中重复调用该新方法。最后,检查结果,并丢弃前几个被JIT编译和其他预热效果扭曲的结果

我知道基准测试有时会很困难,所以我一定是做错了什么。你能发现错误吗

我认为问题在于您的基准测试没有考虑JVM预热。把你的主要方法的主体放在另一个方法中。然后让主方法在循环中重复调用该新方法。最后,检查结果,并丢弃JIT编译和其他预热效果所扭曲的前几个结果。

小提示:

不要使用Math.random执行基准测试,因为结果是不确定的。你需要像新的一样的smth。 始终打印操作的结果。在一次执行中混合基准类型是一种不好的做法,因为它可能导致不同的调用站点优化,但这不是您的情况 double aux=System.nanoTime;-并不是所有的长打都适合双打-正确。 发布环境规范和在其上执行基准测试的硬件 打印“启动测试”,同时启用打印编译-XX:-printcomployment和垃圾收集-verbosegc-XX:+printgcdestails-GC可能在“错误”测试期间启动,足以扭曲结果。 编辑:

我确实检查了生成的汇编程序,但没有一个是真正的原因。没有为Double.valueOf分配,因为该方法是完全内联的,并经过优化-它只使用CPU寄存器。然而,没有硬件规范/JVM,没有真正的答案

我发现JVM 1.6.0.26的通用版本Double有更好的循环展开!,由于更深入的分析,显然需要EA的两个值,可能是宽度/高度。将“宽度/高度”更改为“某些”,结果应有所不同

底线是:除非您知道JVM如何优化和检查生成的机器代码,否则不要使用微基准

免责声明:我不是JVM工程师

小提示:

不要使用Math.random执行基准测试,因为结果是不确定的。你需要像新的一样的smth。 始终打印操作的结果。在一次执行中混合基准类型是一种不好的做法,因为它可能导致不同的调用站点优化,但这不是您的情况 double aux=System.nanoTime;-并不是所有的长打都适合双打-正确。 发布环境规范和在其上执行基准测试的硬件 打印“启动测试”,同时启用打印编译-XX:-printcomployment和垃圾收集-verbosegc-XX:+printgcdestails-GC可能在“错误”测试期间启动,足以扭曲结果。 编辑:

我确实检查了生成的汇编程序,但没有一个是真正的原因。没有为Double.valueOf分配,因为该方法是完全内联的,并经过优化-它只使用CPU寄存器。然而,没有硬件规范/JVM,没有真正的答案

我发现JVM 1.6.0.26的通用版本Double有更好的循环展开!,由于更深入的分析,显然需要EA的两个值,可能是宽度/高度。将“宽度/高度”更改为“某些”,结果应有所不同

底线是:除非您知道JVM如何优化并检查生成的机器代码,否则不要使用微基准 e



免责声明:我不是JVM工程师

这完全是胡乱猜测,但我认为这与将字节复制到堆栈有关。传递原语double需要在堆栈上复制8个字节。传递Double只需要复制指针。

这完全是胡乱猜测,但我认为这与将字节复制到堆栈有关。传递原语double需要在堆栈上复制8个字节。传递Double只需要复制指针。

传递原语Double需要复制堆栈上的8个字节。传递Double只需要复制指针。您应该将测量的任务放在单独的方法中,并运行它们几次,直到编译完成10000/15000就可以了。然后在循环中运行它们并测量。如果我重复运行测试,差异在0.99和1.06之间,通用的速度稍慢。如果我使用-mx12m运行,比率在1.03和1之间。14@Peter:奇怪!我总是得到一个介于0.95和0.98之间的结果!传递原语double需要在堆栈上复制8个字节。传递Double只需要复制指针。您应该将测量的任务放在单独的方法中,并运行它们几次,直到编译完成10000/15000就可以了。然后在循环中运行它们并测量。如果我重复运行测试,差异在0.99和1.06之间,通用的速度稍慢。如果我使用-mx12m运行,比率在1.03和1之间。14@Peter:奇怪!我总是得到一个介于0.95和0.98之间的结果!这不可能是真的-方法是一个调用站点,即静态的-JVM肯定要内联它。如果不是真的,为什么字节比字节更快,但是使用double比double慢?检查生成的程序集-服务器-XX:+UnlockDiagnosticVMOptions-XX:+PrintAssembly-这两个方法都是绝对内联的,并且double.valueOf被省略,即它根本不存在。Bytes.valueOf永远不会分配给btw并且总是缓存。这当然是一个有趣的问题。保罗,我相信你已经找到了答案,但是贝斯特斯肯定有道理,不是吗?我会一直考虑的。。。在任何情况下,对我来说真正重要的答案是拳击其实并不重要,就时间而言。保留访问者的泛型版本会让我的生活更轻松,因为我想处理byte[]、float[]、long[]等等。@Sebastien,如果不能内联,装箱确实很重要,也就是说,有多个实际的2类实现接口,你会看到巨大的差异。一个是你开始有3个可以相对使用的相同频率-你会看到巨大的影响,因为不再有内联和调用站点优化。现在,您需要简单地优化测试用例-这就是不使用微基准的原因。这不可能是真的-该方法是一个调用站点,即静态-JVM肯定要内联它。如果不是真的,为什么字节比字节更快,但是使用double比double慢?检查生成的程序集-服务器-XX:+UnlockDiagnosticVMOptions-XX:+PrintAssembly-这两个方法都是绝对内联的,并且double.valueOf被省略,即它根本不存在。Bytes.valueOf永远不会分配给btw并且总是缓存。这当然是一个有趣的问题。保罗,我相信你已经找到了答案,但是贝斯特斯肯定有道理,不是吗?我会一直考虑的。。。在任何情况下,对我来说真正重要的答案是拳击其实并不重要,就时间而言。保留访问者的泛型版本会让我的生活更轻松,因为我想处理byte[]、float[]、long[]等等。@Sebastien,如果不能内联,装箱确实很重要,也就是说,有多个实际的2类实现接口,你会看到巨大的差异。一个是你开始有3个可以相对使用的相同频率-你会看到巨大的影响,因为不再有内联和调用站点优化。现在,您可以轻松地优化测试用例,这就是不使用微基准的原因。感谢您提供的提示。我将集中讨论最后一个问题,因为我没有想到这个问题。然而,我不认为这是这个结果的原因,因为改变两个循环的顺序不会改变结果。@Sebastien,我想我得到了你的答案谢谢你的提示。我将集中讨论最后一个问题,因为我没有想到这个问题。然而,我不认为这是这个结果的原因,因为改变两个循环的顺序不会改变结果。@Sebastien,我想我得到了你的答案