記一次JAVA 程式最佳化之旅

明月照江江發表於2024-07-31

參與到一個專案中後,這個專案有一個非常棘手的問題,就是程式需要在初次啟動時載入大量資料,高達80G,這會導致成本高築,不得不使用裸金屬伺服器進行部署

上個版本現狀
tps:1790  rt:12.94ms    80G

目標是 tps 不下降的情況下,儘量使記憶體佔用下降

改進1

首次觀察載入資料,發現這個資料具有一定的規律,都是部門或者場地等的程式碼,重複率較高,每個字串出現次數平均80次;可以參考霍夫曼壓縮演算法的邏輯來處理,這裡

於是透過構建資料欄位生成Pair<String, Integer> 類似這樣的資料結構,左邊為字串,右邊為代表字串的數字

當前我們專案字串上限是60K個, 那麼(只這個特例)只要用兩個位元組的資料就可以表達出這個字串的Index(數字)

public class IndexArray {

    private byte[] indexArray = new byte[0];

    private final int UNIT;


    public IndexArray(int size) {
        if (size <= 255) {
            UNIT = 1;
        } else if (size <= 65535) {
            UNIT = 2;
        } else if (size <= 16777215) {
            UNIT = 3;
        } else {
            UNIT = 4;
        }
    }

    public void addIndex(int index) {
        byte[] byteArray = new byte[UNIT];
        for (int i = 0; i < UNIT; i++) {
            byteArray[i] = (byte) ((index >> (i * 8)));
        }

        int srcBytesLength = this.indexArray.length;
        byte[] mergedArray = new byte[srcBytesLength + byteArray.length];
        System.arraycopy(this.indexArray, 0, mergedArray, 0, srcBytesLength);
        System.arraycopy(byteArray, 0, mergedArray, srcBytesLength, byteArray.length);

        this.indexArray = mergedArray;
    }
}

這樣透過兩個位元組就表達了至少40個位元組的字串。

透過這個思路,使記憶體從80G 下降到了 17G

但是遇到了新的挑戰,就是tps 下降了,從tps:1700 下降到 1296

改進2

思考發現自己的定址演算法不是很好,是透過把Index的兩個位元組從位元組陣列中一個個取出,然後轉換成Integer,再來比較的方式處理的,相對耗時;

這裡改進為,將要用於比較的Index 轉換為兩個位元組的資料,在IndexArray上按兩個位元組滑動比較,效率更好

最佳化後
tps:1352 rt:17.35ms

效率上升10%

改進3

在這個介面的演算法邏輯中看到,這是個貪心演算法,由於請求中的引數較多,同時又是反覆取出比較,導致String 轉 Index 這個過程的演算法被反覆呼叫,經檢查這裡也是比較耗時

於是將這個轉化過程改進到進入核心演算法之前,提前轉換

最佳化後
tps: 1489 rt: 15.71ms

效率再上升10%

改進4

由於使用stopwatch列印關鍵步驟耗時過程中發現,在呼叫別的類或者本類中其他方法時,耗時不定但平均下來佔比很大;懷疑是java[CMS(Concurrent Mark Sweep)]記憶體回收導致stop the world的原因導致的耗時,而系統為大記憶體,計算密集型程式;選型使用G1作為垃圾回收器進行實驗

實驗引數:

-server -Xmx85g -Xms85g -Xmn1g -XX:+UseG1GC 
-XX:+UnlockExperimentalVMOptions 
-XX:MaxGCPauseMillis=100 
-XX:InitiatingHeapOccupancyPercent=50 
-XX:G1HeapRegionSize=16M 
-XX:G1NewSizePercent=20 
-XX:G1MaxNewSizePercent=30 
-XX:G1ReservePercent=15 
-XX:G1RSetUpdatingPauseTimePercent=5 
-XX:ParallelGCThreads=16 
-XX:ConcGCThreads=8

(引數含義可以查文件獲得)

效果比較顯著
tps:3369 rt:6.72ms

達到了最佳化目標

相關文章