天天看點

python使用協程解決記憶體瓶頸問題

前言

本來程式跑得好好的,突然快到結尾時程式被強制關閉,報錯 zsh: killed  xxx/bin/python

python使用協程解決記憶體瓶頸問題

後面經查找發現是因為記憶體耗盡,程式強制被關閉,追其溯源,發現使用了類似這樣的代碼:

python使用協程解決記憶體瓶頸問題

從一個檔案夾下讀取全部檔案資料作為list傳回,由于list太大導緻記憶體不足

能不能通過分塊或者惰性加載的思想來疊代nc_datas進而解決記憶體瓶頸問題?

答案是可以的

協程模型

python中的協程采用了generator實作,而generator就類似惰性加載的思想,你調用它一次它才計算一次。比如 x * x 就是generator,它代表了一個理論上無限長的 list 集合,但實際上占用的記憶體隻有目前的值,這也與Java中的Stream是類似的

由于前面記憶體耗盡、程式強制被關閉時,讀資料占了大概10G的記憶體,是以我想分3~4次讀取,這樣每次隻會消耗2~3G的記憶體,不會出現記憶體瓶頸問題

下面給出一個協程簡化模型

# 判斷目前下标c在第幾個段
def check(c,red_lines):
    for i,red_v in enumerate(red_lines):
        if c < red_v:
            return i
    return len(red_lines)

# 比如3個段就會cut出2條分界線
def cut(x,chunck_count):
    per_num = len(x) / chunck_count
    red_lines = []
    # 比如分3段處理就有3-1=2條線(切2刀)
    for i in range(chunck_count - 1):
        red_lines.append((i + 1) * per_num)
    return red_lines

# 讀資料
def read_nc_files():
    
    # 假設一共有10個檔案要讀取
    filepaths = ["1.nc","2.nc","3.nc","4.nc","5.nc","6.nc","7.nc","8.nc","9.nc","10.nc"]
    
    # 切成3次讀取
    red_lines = cut(filepaths,3)
    ans = []
    old = 0
    for i,v in enumerate(filepaths):
        idx = check(i, red_lines)
        if idx != old:
            old = idx
            # 協程傳回,下一步轉到代碼 "for data in datas:" 處進行下一次周遊
            yield ans
            del ans
            ans = []
        ans.append(v)

    yield ans


def test():
    
    # 由于python檢測到read_nc_files函數裡有yield關鍵字
    # 是以将其作為生成器generator傳回,即這裡datas不是list,而是一個generator
    datas = read_nc_files()

    # 對于周遊generator,周遊一次計算一次(傳回一次)
    # 而不是一次性把所有datas傳回
    # 周遊後的下一步将跳轉到代碼 "yield ans" 處并繼續往下執行
    for data in datas:
        print(data)
        
test()
           

實驗将10個檔案切成3次讀取,列印結果:

python使用協程解決記憶體瓶頸問題

對于11、12、13個檔案測試,同樣列印結果:

python使用協程解決記憶體瓶頸問題

可以看到采用協程的記憶體瓶頸解決方案成功