`zipimport._zstd_decompress()` repeatedly processes remaining input for concatenated frames
還沒有人認領這個 Issue。
- 主要語言
- Python
- 星號
- 77.2k
- 分支
- 35.9k
- PR 合併指標
- PR 指標待擷取
描述
Bug description
zipimport._zstd_decompress() handles concatenated Zstandard frames by creating a new _zstd.ZstdDecompressor for each frame. Each decompressor is passed the entire remaining compressed suffix, and the next iteration obtains the same suffix, minus the frame just decoded, through unused_data.
As a result, when a zstd-compressed ZIP entry using compression method 93 contains many concatenated frames, most of the compressed input is passed to decompressor calls repeatedly. For N similarly sized frames, the cumulative amount of input handled across those calls grows quadratically rather than linearly with the size of the entry.
Reproducer and local results
import time
import zipimport
from compression import zstd
FRAMES = 64_000
frame = zstd.compress(b"x")
data = frame * FRAMES
input_sizes = []
original = zipimport._get_zstd_decompressor_class()
class RecordingDecompressor:
def __init__(self, *args, **kwargs):
self._decompressor = original(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._decompressor, name)
def decompress(self, data, max_length=-1):
input_sizes.append(memoryview(data).nbytes)
return self._decompressor.decompress(data, max_length)
zipimport._zstd_decompressor_class = RecordingDecompressor
start = time.perf_counter()
try:
output = zipimport._zstd_decompress(data)
finally:
elapsed = time.perf_counter() - start
zipimport._zstd_decompressor_class = original
print(f"input size: {len(data):,} bytes")
print(f"output size: {len(output):,} bytes")
print(f"decompress calls: {len(input_sizes):,}")
print(f"cumulative input passed: {sum(input_sizes):,} bytes")
print(f"amplification: {sum(input_sizes) / len(data):,.2f}x")
print(f"elapsed: {elapsed:.4f} seconds")
Local result:
input size: 640,000 bytes
output size: 64,000 bytes
decompress calls: 64,000
cumulative input passed: 20,480,320,000 bytes
amplification: 32,000.50x
elapsed: 1.5364 seconds
After fix:
input size: 640,000 bytes
output size: 64,000 bytes
decompress calls: 64,000
cumulative input passed: 640,000 bytes
amplification: 1.00x
elapsed: 0.2333 seconds
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-155154
貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
研究方向
進入點是 zipimport._zstd_decompress();首先執行提供的串聯 frame 重現程式,並檢查每次解壓縮器呼叫收到的輸入。完成的條件是輸出保持正確,同時傳入的累積輸入呈線性成長,就像報告中的 1.00x 結果一樣。連結的 PR gh-155154 表示相關工作已經在進行中。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- python
- 領域
- backend
- Issue 類型
- 缺陷
- 難度
- 3/5
- 預估耗時
- 1-2 天
- 活躍度
- 停滯
- 描述清晰度
- 描述清楚
- 新手友好度
- 25/100