靜態初始化中不能放入繁重計算,否則會變慢!

banq發表於2019-04-10

在類初始化期間計算不可變資料結果,並將結果儲存在static final欄位中是一種非常常見的做法。實際上,這正是靜態初始化器的設計目標。
以下是在初始化時構建一些靜態表的典型示例:

public class StaticExample {
    static final long[] TABLE = new long[100_000_000];

    static {
        TABLE[0] = 0;
        for (int i = 1; i < TABLE.length; i++) {
            TABLE[i] = nextValue(TABLE[i - 1]);
        }
    }

    private static long nextValue(long seed) {
        return seed * 0x123456789L + 11;
    }

    ...
}

在我的JDK 11.0.1膝上型電腦上,靜態初始化程式在大約540毫秒內填充100M元素的陣列。
現在讓我們簡單地刪除static並填充建構函式中的陣列。

public class NonStaticExample {
    final long[] TABLE = new long[100_000_000];

    {
        TABLE[0] = 0;
        for (int i = 1; i < TABLE.length; i++) {
            TABLE[i] = nextValue(TABLE[i - 1]);
        }
    }

    private static long nextValue(long seed) {
        return seed * 0x123456789L + 11;
    }

    public static void main(String[] args) {
        new NonStaticExample();
    }
}

建構函式在138毫秒內填充類似的陣列。幾乎快4倍!

為什麼靜態初始化器會變慢?這必須與JIT編譯有關,詳細分析點選標題見原文。

解決方法非常簡單:
只是不要直接在未初始化的類中進行繁重的計算。如果將計算邏輯放在沒有靜態初始化程式的輔助類中,它將不會受到效能損失的影響。

public class StaticExample {
    static final long[] TABLE = Helper.prepareTable();

    private static class Helper {

        static long[] prepareTable() {
            long[] table = new long[100_000_000];
            for (int i = 1; i < table.length; i++) {
                table[i] = nextValue(table[i - 1]);
            }
            return table;
        }

        static long nextValue(long seed) {
            return seed * 0x123456789L + 11;
        }
    }
}


 

相關文章