java中enum_Java中的微优化。 好,好,慢Enum

java中enum

Enums are crucial part of every application larger than “Hello World“. We use them everywhere. They are actually very useful: they restrict the input, allow you to compare the values by the reference, provide compile-time checks, and make the code easier to read. However, using Enums has a few performance downsides. And we’ll review them in this post.

枚举是每个大于“ Hello World”的应用程序的重要组成部分。 我们到处都使用它们。 它们实际上非常有用:它们限制了输入,允许您按引用比较值,提供编译时检查,并使代码更易于阅读。 但是,使用Enums有一些性能下降。 我们将在这篇文章中对其进行审查。

(Please consider all the code below from the point of performance)

(请从性能角度考虑以下所有代码)

(Please do not focus on numbers, they are just examples to prove the point)

(请不要专注于数字,它们只是证明这一点的例子)

枚举值 (Enum.valueOf)

In Blynk, one of the most popular features is called “Widget Property”. It’s when you can change the appearance of the visual widget in real-time by sending from the hardware the command like that:

Blynk中 ,最受欢迎的功能之一是“ 窗口小部件属性”。 此时,您可以通过从硬件发送如下命令来实时更改可视窗口小部件的外观:

Blynk.setProperty(v1, “LABEL”, “My New Label”);

Let’s take a look at existing Enum from our server:

让我们看一下服务器中的现有Enum:

enum WidgetProperty {
LABEL,
COLOR,
ON_LABEL,
OFF_LABEL,
MIN,
MAX
}

And now look at this basic code:

现在看下面的基本代码:

String inWidgetProperty;
...
WidgetProperty property = WidgetProperty.valueOf(inWidgetProperty)

Do you see what is wrong here?

你看到这里有什么问题吗?

Let’s rewrite the above code and create our own method based on the switch statement:

让我们重写上面的代码,并基于switch语句创建我们自己的方法:

public static WidgetProperty valueOfSwitch(String value) {
        switch (value) {
            case "LABEL" :
                return WidgetProperty.LABEL;
            case "COLOR" :
                return WidgetProperty.COLOR;
            case "ON_LABEL" :
                return WidgetProperty.ON_LABEL;
            case "OFF_LABEL" :
                return WidgetProperty.OFF_LABEL;
            case "MIN" :
                return WidgetProperty.MIN;
            case "MAX" :
                return WidgetProperty.MAX;
        }
        return null;
    }

This method isn’t precisely the same, as it doesn’t throw the IllegalArgumentException in case of an unknown value. However, it’s done on purpose. Because, as you know, throwing and creating the exception (because of the stack trace) is an expensive operation.

此方法并不完全相同,因为在未知值的情况下不会抛出IllegalArgumentException 。 但是,这是有目的的。 如您所知,因为抛出和创建异常(由于堆栈跟踪)是一项昂贵的操作。

Let’s make a benchmark:

让我们做一个基准:

@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 10, time = 1)
public class EnumValueOf {


    @Param({"LABEL", "ON_LABEL", "MAX"})
    String strParams;


    @Benchmark
    public WidgetProperty enumValueOf() {
        return WidgetProperty.valueOf(strParams);
    }


    @Benchmark
    public WidgetProperty enumSwitch() {
        return WidgetProperty.valueOfSwitch(strParams);
    }


}

Results (lower score means faster):

结果(分数越低意味着速度越快):

JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10


Benchmark                (strParams)  Mode  Cnt   Score   Error  Units
EnumValueOf.enumSwitch         LABEL  avgt   80   7.149 ± 0.030  ns/op
EnumValueOf.enumSwitch      ON_LABEL  avgt   80   7.758 ± 0.077  ns/op
EnumValueOf.enumSwitch           MAX  avgt   80   6.948 ± 0.083  ns/op
EnumValueOf.enumValueOf        LABEL  avgt   80  15.659 ± 0.450  ns/op
EnumValueOf.enumValueOf     ON_LABEL  avgt   80  14.734 ± 0.542  ns/op
EnumValueOf.enumValueOf          MAX  avgt   80  15.153 ± 0.578  ns/op

Hm… Our custom method with the switch statement is two times faster than Enum.valueOf. Something is wrong here. We need to go deeper.

嗯……我们带有switch语句的自定义方法比Enum.valueOf两倍。 这里不对劲。 我们需要更深入。

As you probably know, enum classes don’t have the Java valueOf() method. The compiler generates it during the compilation. So we need to look into the byte code of the generated class to understand what is going on there:

您可能知道, 枚举类没有Java valueOf()方法。 编译器在编译过程中生成它。 因此,我们需要查看生成的类的字节码,以了解发生了什么:

 LDC Lstring/WidgetProperty;.class
ALOAD 0
INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
CHECKCAST string/WidgetProperty
ARETURN

Looks like java.lang.Enum.valueOf method is invoked inside WidgetPtoperty.valueOf method. Fortunately, this method is present in the Java code. Let’s check it:

看起来在WidgetPtoperty.valueOf方法内部调用了java.lang.Enum.valueOf方法。 幸运的是,该方法存在于Java代码中。 让我们检查一下:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

And enumConstantDirectory:

enumConstantDirectory

Map<String, T> enumConstantDirectory() {
        Map<String, T> directory = enumConstantDirectory;
        if (directory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            directory = new HashMap<>((int)(universe.length / 0.75f) + 1);
            for (T constant : universe) {
                directory.put(((Enum<?>)constant).name(), constant);
            }
            enumConstantDirectory = directory;
        }
        return directory;
    }
    private transient volatile Map<String, T> enumConstantDirectory;

Aha, so all we have here is a regular volatile HashMap with the enum names as the keys. And it looks like in that particular case the switch statement just outperforms the HashMap.get() method.

啊哈,所以我们这里只是一个以枚举名称为键的常规volatile HashMap 。 在特定情况下, switch语句的性能似乎优于HashMap.get()方法。

Let’s add valueOf with own HashMap implementation to the test as well:

让我们也将带有自己的HashMap实现的valueOf添加到测试中:

private final static Map<String, WidgetProperty> cache;
static {
cache = new HashMap<>();
for (WidgetProperty widgetProperty : values()) {
cache.put(widgetProperty.name(), widgetProperty);
}
}public static WidgetProperty enumMap(String value) {
return cache.get(value);
}

Results (lower score means faster, enumMap results attached to the prev test):

结果(分数越低意味着速度越快,将enumMap结果附加到prev测试中):

JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10


Benchmark                (strParams)  Mode  Cnt   Score   Error  Units
EnumValueOf.enumMap            LABEL  avgt   10  11.749 ± 0.761  ns/op
EnumValueOf.enumMap         ON_LABEL  avgt   10  11.778 ± 0.306  ns/op
EnumValueOf.enumMap              MAX  avgt   10  10.517 ± 0.056  ns/op
EnumValueOf.enumSwitch         LABEL  avgt   80   7.149 ± 0.030  ns/op
EnumValueOf.enumSwitch      ON_LABEL  avgt   80   7.758 ± 0.077  ns/op
EnumValueOf.enumSwitch           MAX  avgt   80   6.948 ± 0.083  ns/op
EnumValueOf.enumValueOf        LABEL  avgt   80  15.659 ± 0.450  ns/op
EnumValueOf.enumValueOf     ON_LABEL  avgt   80  14.734 ± 0.542  ns/op
EnumValueOf.enumValueOf          MAX  avgt   80  15.153 ± 0.578  ns/op

Own HashMap implementation seems faster than regular Enum.valueOf() but still slower than the switch statement.

自己的HashMap实现似乎比常规的Enum.valueOf()快,但仍比switch语句慢。

Interesting… I expected that the compiler-generated valueOf method would be more effective. It already has all the required info during the compilation, so the compiler should be able to make a more efficient code.

有趣的是……我希望编译器生成的valueOf方法会更有效。 它在编译期间已经具有所有必需的信息,因此编译器应该能够编写更有效的代码。

Enum.values() (Enum.values())

Now, let’s consider another example. It’s not so popular as above one, but still present in many projects:

现在,让我们考虑另一个示例。 它不像上面的那样流行,但是仍然存在于许多项目中:

for (WidgetProperty property : WidgetProperty.values()) {
...
}

Do you see what is wrong here?

你看到这里有什么问题吗?

Again, Enum.values() method is generated during the compilation, so to understand what is wrong there we have to look at the byte code one more time:

同样,在编译过程中会生成Enum.values()方法,因此要了解那里出了什么问题,我们必须再看一次字节码:

 GETSTATIC string/WidgetProperty.$VALUES : [Lstring/WidgetProperty;
INVOKEVIRTUAL [Lstring/WidgetProperty;.clone ()Ljava/lang/Object;
CHECKCAST [Lstring/WidgetProperty;
ARETURN

You don’t need to be a byte code expert to see the .clone() method invocation. So, every time you call the Enum.values() method — the array with the enum values is always copied.

您无需成为字节码专家即可查看.clone()方法的调用。 因此,每次调用Enum.values()方法时,总是会复制带有枚举值的数组。

The solution is pretty obvious — we can cache the Enum.values() method result like that:

解决方案非常明显-我们可以像这样缓存Enum.values()方法的结果:

public static final WidgetProperty[] values = values();

And use the cache field instead. Let’s check it with the benchmark:

并改用缓存字段。 让我们用基准进行检查:

@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 10, time = 1)
public class EnumValues {


    @Benchmark
    public void enumValuesMethod(Blackhole bh) {
        for (WidgetProperty property : WidgetProperty.values()) {
            bh.consume(property);
        }
    }


    @Benchmark
    public void enumValuesVariable(Blackhole bh) {
        for (WidgetProperty property : WidgetProperty.values) {
            bh.consume(property);
        }
    }


}

I used the loop in that benchmark because, usually, the Enum.values() is used within the loop.

我在该基准测试中使用了循环,因为通常在循环中使用Enum.values()

Results (lower score means faster):

结果(分数越低意味着速度越快):

JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10


Benchmark                      Mode  Cnt   Score   Error  Units
EnumValues.enumValuesMethod    avgt   10  21.576 ± 0.225  ns/op
EnumValues.enumValuesVariable  avgt   10  16.811 ± 0.095  ns/op

The performance difference here is ~30%. That’s because the loop iteration takes some CPU as well. However, what is more important: after our fix, no allocation happens. And that’s even more important than gained speed improvement.

此处的性能差异约为30%。 那是因为循环迭代也需要一些CPU。 但是,更重要的是:修复后,没有分配发生。 这甚至比提高速度更为重要。

You may wonder, why Enum.values() is implemented in this way? Well, the explanation is quite simple. Without copy() method anyone could change the content of the returned array. And that could lead to many problems. So returning the same reference to the array with values that can be easily replaced isn’t the best idea. Immutable arrays would solve that issue, but Java doesn’t have one.

您可能想知道,为什么以这种方式实现Enum.values() ? 好吧,解释很简单。 没有copy()方法,任何人都可以更改返回数组的内容。 这可能会导致许多问题。 因此,将相同的引用返回到具有易于替换的值的数组并不是最好的主意。 不可变的数组可以解决这个问题,但是Java没有。

Yes, this change decreases the readability a bit and you don’t need to use it with every enum. It’s necessary only for really hot paths.

是的,此更改会稍微降低可读性,并且您不需要在每个枚举中都使用它。 仅在真正热的路径上才需要。

Check for example this PR for Apache Cassandra jdbc driver, mssql-jdbc driver PR or this PR for http server.

例如,检查此PR for Apache Cassandra jdbc驱动程序mssql-jdbc驱动程序PR该HTTP服务器PR

结论 (Conclusion)

  • For hot paths, consider using your own implementation of the Enum.valueOf() method based on the switch statement. As an additional bonus, you could avoid throwing the Exception during the wrong input. That could be a game-changer for some flows

    对于热路径,请考虑基于switch语句使用您自己的Enum.valueOf()方法的实现。 另外,您可以避免在输入错误时抛出Exception 。 这可能会改变某些流程

  • For hot paths, go with the cached Enum.values() field. This will allow you to decrease the allocation rate

    对于热路径,请使用缓存的Enum.values()字段。 这将使您降低分配率

Here is a source code of benchmarks, so that you can try it yourself.

这是基准测试的源代码 ,因此您可以自己尝试。

Thank you for your attention, and stay tuned.

感谢您的关注,敬请期待。

Previous post: Micro optimizations in Java. String.equalsIgnoreCase()

上一篇: Java中的微优化。 String.equalsIgnoreCase()

翻译自: https://medium.com/javarevisited/micro-optimizations-in-java-good-nice-and-slow-enum-261e6f77bd2e

java中enum

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值