Enum::values() の速度計測 (Java)

JavaのEnumクラスのvalues() メソッドですが、
毎回配列コピーが走っているようなので、
もし、速度を気にする場合はstatic変数などにキャッシュさせておくと良いです。

単純にvalues()メソッドが返した配列をキャッシュしている場合と、
していない場合で1000万回のforループでの処理速度を測定してみました。

一応、このくらいの差があることは認識しておいて損はないでしょう。

テスト用のEnumクラス

列挙するものが多いほど、values()メソッドでコピーする量はもちろん増えます。
今回は30個くらいの要素でやってみました。

enum SampleEnum
{
    HOGE01,
    HOGE02,
    HOGE03,
    HOGE04,
    HOGE05,
    HOGE06,
    HOGE07,
    HOGE08,
    HOGE09,
    HOGE10,
    HOGE11,
    HOGE12,
    HOGE13,
    HOGE14,
    HOGE15,
    HOGE16,
    HOGE17,
    HOGE18,
    HOGE19,
    HOGE20,
    HOGE21,
    HOGE22,
    HOGE23,
    HOGE24,
    HOGE25,
    HOGE26,
    HOGE27,
    HOGE28,
    HOGE29,
    HOGE30;

    // values()メソッドの返り値をstatic変数でキャッシュ
    public static final SampleEnum[] VALUES = values();
}

Enum::values() メソッドの場合

public static void test1()
{
    long processStart = System.nanoTime();

    for (int i = 0; i < 10000000; i++)
    {
        // values() メソッドを毎回使う
        SampleEnum[] values = SampleEnum.values();
    }

    long processEnd = System.nanoTime();
    System.out.println(String.format("PROCESS_TIME: %d[ms]", (processEnd - processStart) / 1000000));
}

Enum::values() をキャッシュした場合

public static void test2()
{
    long processStart = System.nanoTime();

    for (int i = 0; i < 10000000; i++)
    {
        // キャッシュを毎回使う
        SampleEnum[] values = SampleEnum.VALUES;
    }

    long processEnd = System.nanoTime();
    System.out.println(String.format("PROCESS_TIME: %d[ms]", (processEnd - processStart) / 1000000));
}

測定結果

1000万回のループでの実行速度を測定
キャッシュありの方は、ただstatic変数を参照しているだけので、早いのは当たり前です。
values() メソッドを使う場合は、配列生成のコストが掛かっている分、遅くなります。

Java version Test case Process time
1.8.0_65 values()毎回使う 368[ms]
1.8.0_65 キャッシュあり 2[ms]
9.0.1 values()毎回使う 20[ms]
9.0.1 キャッシュあり 2[ms]
10 values()毎回使う 22[ms]
10 キャッシュあり 3[ms]

Java9, 10 では速度が早くなっている?

最近、Java10がリリースされているので、Java10でも試してみたところ、
なんと、Java9以降から Enum::values() の速度が上がってる??
(values() メソッドの実装が変わったのかな?)

Mapでキャッシュさせたい場合

IDなどのメンバ変数持ちのEnumであれば、そこからEnumクラスを取得したい事も多々あるでしょう。
Mapの形でキャッシュしておくことで効率がよくなります。

enum IdEnum {
    PIYO1(1),
    PIYO2(2),
    PIYO3(3);

    private int _id;

    IdEnum(int id) {
        this._id = id;
    }

    // values() を好きなキーでマップにしてキャッシュ
    public static final Map<Integer, IdEnum> ID_TO_ENUMs = Arrays.stream(values())
            .collect(Collectors.toMap(o -> o._id, o -> o));
}
IdEnum one = IdEnum.ID_TO_ENUMs.get(1); // PIYO1 を取得

おしまい

Java9, 10では、Enum::values() の速度が早くなっているみたいなのと、
そもそも、そこまで大したコストではないのであまり気にしなくても良いとは思います。

Stringなんかも似たようなもので、リテラルから毎回生成されていたりします。
(String Constant Pool である程度使いまわされるようですが)

本当に速度が気になるところでだけキャッシュさせるのがベストでしょう。

コメントを残す