静态空数组实例的性能优势 [英] Performance benefits of a static empty array instance

查看:48
本文介绍了静态空数组实例的性能优势的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

将空数组的常量返回值提取为静态常量似乎是常见的做法.像这里:

It seems common practice to extract a constant empty array return value into a static constant. Like here:

public class NoopParser implements Parser {
    private static final String[] EMPTY_ARRAY = new String[0];

    @Override public String[] supportedSchemas() {
        return EMPTY_ARRAY;
    }

    // ...
}

之所以这样做是出于性能方面的考虑,因为直接返回new String[0]会在每次调用该方法时创建一个新的数组对象-真的吗?

Presumably this is done for performance reasons, since returning new String[0] directly would create a new array object every time the method is called – but would it really?

我一直在想这样做是否真的有可衡量的性能优势,还是只是过时的民间智慧.空数组是不可变的. VM是否无法将所有空的String阵列合并为一个?虚拟机能否不能使new String[0]基本免费?

I've been wondering if there really is a measurable performance benefit in doing this or if it's just outdated folk wisdom. An empty array is immutable. Is the VM not able to roll all empty String arrays into one? Can the VM not make new String[0] basically free of cost?

将这种做法与返回空字符串进行对比:我们通常非常乐意编写return "";而不是return EMPTY_STRING;.

Contrast this practice with returning an empty String: we're usually perfectly happy to write return "";, not return EMPTY_STRING;.

推荐答案

我对这两个习惯用法在实际的实际情况下的实际性能差异最感兴趣.我没有进行微基准测试的经验(它可能不是解决此类问题的正确工具),但我还是尝试了一下.

I'm most interested in the actual performance difference between these two idioms in practical, real-world situations. I have no experience in micro-benchmarking (and it is probably not the right tool for such a question) but I gave it a try anyway.

此基准模拟了一种较为典型的现实"设置.仅查看返回的数组,然后将其丢弃.没有参考信息徘徊,不需要参考相等性.

This benchmark models a somewhat more typical, "realistic" setting. The returned array is just looked at and then discarded. No references hanging around, no requirement for reference equality.

一个接口,两个实现:

public interface Parser {
    String[] supportedSchemas();
    void parse(String s);
}

public class NoopParserStaticArray implements Parser {
    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    @Override public String[] supportedSchemas() {
        return EMPTY_STRING_ARRAY;
    }

    @Override public void parse(String s) {
        s.codePoints().count();
    }
}

public class NoopParserNewArray implements Parser {
    @Override public String[] supportedSchemas() {
        return new String[0];
    }

    @Override public void parse(String s) {
        s.codePoints().count();
    }
}

以及JMH基准测试:

import org.openjdk.jmh.annotations.Benchmark;

public class EmptyArrayBenchmark {
    private static final Parser NOOP_PARSER_STATIC_ARRAY = new NoopParserStaticArray();
    private static final Parser NOOP_PARSER_NEW_ARRAY = new NoopParserNewArray();

    @Benchmark
    public void staticEmptyArray() {
        Parser parser = NOOP_PARSER_STATIC_ARRAY;
        for (String schema : parser.supportedSchemas()) {
            parser.parse(schema);
        }
    }

    @Benchmark
    public void newEmptyArray() {
        Parser parser = NOOP_PARSER_NEW_ARRAY;
        for (String schema : parser.supportedSchemas()) {
            parser.parse(schema);
        }
    }
}

我的机器Java 1.8.0_51(HotSpot VM)上的结果:

The result on my machine, Java 1.8.0_51 (HotSpot VM):

Benchmark                              Mode  Cnt           Score          Error  Units
EmptyArrayBenchmark.staticEmptyArray  thrpt   60  3024653836.077 ± 37006870.221  ops/s
EmptyArrayBenchmark.newEmptyArray     thrpt   60  3018798922.045 ± 33953991.627  ops/s
EmptyArrayBenchmark.noop              thrpt   60  3046726348.436 ±  5802337.322  ops/s

在这种情况下,两种方法之间没有显着差异.实际上,它们与无操作情况是没有区别的:显然,JIT编译器认识到返回的数组始终为空,并完全优化了循环!

There is no significant difference between the two approaches in this case. In fact, they are indistinguishable from the no-op case: apparently the JIT compiler recognises that the returned array is always empty and optimises the loop away entirely!

parser.supportedSchemas()插入黑洞而不是在黑洞上循环,可以使静态数组实例的方法获得约30%的优势.但是它们的大小绝对相同:

Piping parser.supportedSchemas() into the black hole instead of looping over it, gives the static array instance approach a ~30% advantage. But they're definitely of the same magnitude:

Benchmark                              Mode  Cnt           Score         Error  Units
EmptyArrayBenchmark.staticEmptyArray  thrpt   60   338971639.355 ±  738069.217  ops/s
EmptyArrayBenchmark.newEmptyArray     thrpt   60   266936194.767 ±  411298.714  ops/s
EmptyArrayBenchmark.noop              thrpt   60  3055609298.602 ± 5694730.452  ops/s

也许最终答案是通常的取决于".我有一种预感,在许多实际情况下,将数组创建因素排除在外的 性能收益并不显着.

Perhaps in the end the answer is the usual "it depends". I have a hunch that in many practical scenarios, the performance benefit in factoring out the array creation is not significant.

我认为这很公平

  • 如果方法协定使您每次可以自由返回一个新的空数组实例,并且
  • 除非您需要避免出现问题性或病理性使用模式和/或以达到理论上的最大性能为目标,否则

然后直接返回new String[0]就可以了.

then returning new String[0] directly is fine.

我个人很喜欢return new String[0];的表现力和简洁性,而不必引入额外的静态字段.

Personally, I like the expressiveness and concision of return new String[0]; and not having to introduce an extra static field.

由于一些奇怪的巧合,我写这篇文章的一个月后,一位真正的性能工程师调查了这个问题:请参见本节在阿列克谢·希普伊尔夫(AlexeyShipilёv)的博客文章远古智慧阵列"中

By some strange coincidence, a month after I wrote this a real performance engineer investigated the problem: see this section in Alexey Shipilёv's blog post 'Arrays of Wisdom of the Ancients':

如预期的那样,在很小的集合大小上可以观察到的唯一效果,这仅是对new Foo[0]的边际改进.宏伟的计划.作为微小的微小优化,在一些紧凑的代码中可能有意义,但我不在乎.

As expected, the only effect whatsoever can be observed on a very small collection sizes, and this is only a marginal improvement over new Foo[0]. This improvement does not seem to justify caching the array in the grand scheme of things. As a teeny tiny micro-optimization, it might make sense in some tight code, but I wouldn’t care otherwise.

解决了.我将勾选标记,并将其专用于Alexey.

That settles it. I'll take the tick mark and dedicate it to Alexey.

这篇关于静态空数组实例的性能优势的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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