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

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

问题描述

将常量空数组返回值提取为静态常量似乎很常见.喜欢这里:

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.

巧合的是,在我写这篇文章一个月后,一位真正的性能工程师调查了这个问题:见 此部分在 Alexey Shipilё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.

这就解决了.我会把那个勾号献给阿列克谢.

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

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

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