簡體   English   中英

Java GZIPOutputStream 似乎分配了不必要的字節數組?

[英]Java GZIPOutputStream appears to allocate unnecessary byte arrays?

我有一個應用程序可以處理文本文件並存儲它們,並為了節省一些空間,它會壓縮文件。 所以我有一些鏈式輸出流,其中之一是管理壓縮的java.util.zip.GZIPOutputStream

為了確保我沒有在某處浪費內存,我使用 async profiler/intellij 對我的進程進行了分析,該文件在一個小循環中包含大約 6MB 的隨機數據一段時間。 作為參考,我使用的是 Temurin JDK18。

我很驚訝地看到GZIPOutputStream分配了很多內存(通過父方法): gzip 輸出樣本 1,601,318,568樣本

這有點奇怪。 我知道GZIPOutputStream / DeflaterOutputStream使用緩沖區,但為什么要分配這么多? 我深入研究了代碼。 我注意到java.util.zip.DeflaterOutputStream中的父方法在寫入一個字節時會這樣做:

    public void write(int b) throws IOException {
        byte[] buf = new byte[1];
        buf[0] = (byte)(b & 0xff);
        write(buf, 0, 1);
    }

那么,它為每個字節創建一個新的單字節數組嗎? 這絕對看起來會是很多分配? 為了看看它是否有所不同,我使用一個名為LowAllocGzipOutputStream的新類擴展了GZIPOutputStream ,並使用了如下覆蓋方法:

    private final byte[] singleByteBuff = new byte[1];

    @Override
    public void write(int b) throws IOException {
        singleByteBuff[0] = (byte)(b & 0xff);
        write(singleByteBuff, 0, 1);
    }

然后我用我的測試用例再次分析它,看看會發生什么。 數據完全不同: 低分配樣本 162,262,880樣本

這是一個相當大的分配減少, -1,439,055,688樣本。

所以我留下了一些我沒有找到答案的問題:

  1. 為什么GZIPOutputStream / DeflaterOutputStream會這樣分配byte[] 這是 JDK 附帶的一個類,所以我確信它已經過深入分析和審查,但以我天真的理解,它似乎是不必要的浪費? 單字節數組最終會被熱點優化掉嗎? 它真的不會給垃圾收集器增加壓力嗎?
  2. 我緩存singleByteBuff方法是否有負面影響? 到目前為止,我似乎無法想到它會導致任何問題。 我發現它的好處是我的應用程序的內存配置文件不再受DeflaterOutputStream byte[]分配支配。

在花了更多時間研究流之后,我將嘗試回答我自己的問題,但需要進行一些猜測:

據我所知,如果您完全關心性能,基本上只有一種理智的方法可以調用 OutputStream,這就是方法

public void write(byte[] b, int off, int len)

有幾個原因:

  1. 對於 gzip 之類的東西,一次處理更多字節可能更有效
  2. 在可以節省內存的地方重用緩沖區
  3. 更少的函數調用

作為普通的 Java 開發人員,#3 不太明顯。 通常你不會過多地考慮函數調用。 但是,如果您一次一個字節地處理 10 億字節,那就加起來了! 函數調用、少量工作等等,所有這些你可以少做的事情都不必要地加起來。
在我的應用程序的原始設計中,我考慮可以將 OutputStreams 視為一個狀態機,其中每個輸入部分都是一個字節。 當涉及緩沖區時,這可能只是考慮數據流的錯誤方式。

要使 OutputStream 工作,您必須實現的唯一方法是:

public abstract void write(int b)

有時你確實需要寫一個字節。 使用這種方法很方便。 然而,這種方法的方便和簡單是一個陷阱。 它可以使用,但如果您關心性能,則不應使用它。 如果您在生產中使用它,肯定會發生太多不必要的工作。

這就是我認為 GZIPOutputStream 背后的原因與此方法有關的原因。 如果您曾經實現過一個有趣的 OutputStream,那么很快就會變得顯而易見的是,您確實希望所有邏輯都流入其中一個方法。 但是,如果你關心任何事情,你永遠不會選擇write(int b) ,考慮到它的擴展性有多差,那將是瘋狂的。 因此,這些更簡單的方法在實施時無需多加注意。 如果您只是多次寫入一個字節,那么幾個數組分配是無關緊要的。

在我的問題示例中,我通過添加單字節緩沖區為 GZIPOutputStream 的write(int b)制定了一種更有效的方法。 而且,據我所知,它更有效率! 但是,如果您真的希望您的代碼高效運行,那么您仍然永遠不會使用這種方法,無論它如何優化。 你的程序仍然會做太多不必要的工作。
這就是我認為設計思想的來源。 write(int b)的存在只是為了讓您可以在技術上實現 OutputStream 並允許單字節寫入,但您幾乎總是應該避免這種情況,那么為什么要優化一個固有缺陷的方法呢?

也就是說,任何這些方法中的一點 javadoc 都可以幫助我在這里學習。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM