與已經初始化的陣列相比,我最近注意到在處理新初始化的 Numpy 陣列時速度明顯放緩。一般來說,初始化陣列需要更長的時間似乎是合乎邏輯的,但我沒想到會有這么大的差異。該片段是我需要的函式的示意圖部分,僅創建dim3的這兩行代碼就占用了函式總運行時間的一半左右。
import numpy as np
mask = np.where(np.random.rand(150,150) > 0.98)
very_important_data = np.random.rand(len(mask[0]), 1000)
dim3 = np.zeros((150,150,1000))
%timeit dim3[mask] = very_important_data # --> 114 μs ± 5.24 μs per loop
%timeit dim3 = np.zeros((150,150,1000)); dim3[mask] = very_important_data # --> 9.4 ms ± 585 μs per loop
有沒有更有效的方法來預初始化 dim3 矩陣?還是一種在分配新值之前將矩陣保存在記憶體中的有效方法?
謝謝!
uj5u.com熱心網友回復:
要了解是否有更有效的方法,首先需要了解為什么當前代碼很慢。
Numpy 方法創建陣列,如np.zerosor np.empty(或任何創建臨時陣列的方法,如乘法,加法等)從 CPython 分配器請求記憶體緩沖區,將其轉發到默認的 libc 分配器(與作業系統不同)或自定義分配器(如果有)。np.zeros請求一個預填充零的緩沖區,而np.empty只請求一個原始緩沖區。
默認分配器在平臺(主要是作業系統)方面表現不同。在 Windows 上,它向作業系統請求記憶體并系統地釋放它以獲得大緩沖區,而 Mac 和 Linux 的默認記憶體分配器往往更加保守:它們保留相當大的本地記憶體塊并嘗試盡可能多地重用它們,而不是將空間釋放給作業系統。
此默認策略對性能和記憶體使用有巨大影響。實際上,當從 Numpy 請求一個零填充的記憶體緩沖區并且從先前分配的空間(尚未釋放到作業系統)中回收該緩沖區時,分配器需要將所有值填充為 0。但是,當直接從作業系統請求零填充記憶體時,作業系統可以回傳一個虛擬記憶體緩沖區,只有在特定記憶體頁面上執行第一次觸摸時才會延遲填充該緩沖區。這意味著對于大型陣列的分配可以更快,但是用零填充陣列的開銷被延遲. 最后,只要所有頁面都被讀取/寫入(即陣列被完全讀取或寫入某些值),就會支付填充陣列的開銷。實際上,這種惰性記憶體填充比由于page-faults由分配器回收緩沖區更昂貴。一些作業系統預填充記憶體塊(可能在單獨的執行緒中)以加速此類零填充緩沖區請求。因此,您應該非常小心對應用程式進行基準測驗的方式。
實際上,出于安全原因,在主流平臺上(默認情況下在 Windows、Linux 和 Mac 上)向作業系統請求的記憶體總是用零填充:一個行程先前分配、填充和釋放的記憶體一定不能從另一個行程訪問因為記憶體塊可能包含敏感資訊(例如,您的瀏覽器可以將密碼存盤在記憶體中,并且您不希望 Numpy python 腳本能夠在沒有任何權限的情況下讀取它們)。這種零填充通常在頁面錯誤時完成。因此,當從作業系統請求陣列時,呼叫np.empty或給出相同的結果。np.zeros然而,當陣列被分配器回收時,那么np.empty可以更快,并且(通常)無需支付頁面錯誤開銷(只要記憶體頁面沒有存盤在記憶體不足時的其他位置,例如交換中,頁面錯誤每頁執行一次)。
簡而言之,只要您請求創建一個新陣列并讀取/寫入所有目標陣列,就沒有辦法(僅來自 Python)來加速陣列的創建。使用自定義系統分配器并沒有多大幫助,因為無論如何都必須填充陣列。如果您可以逐步支付開銷,那么您需要使用手動 memmap。否則,您可以預先分配一些緩沖區并自己回收它們。它可以更快,因為您可能不需要將它們完全填充為零,并且您不會支付頁面錯誤的成本。天下沒有免費的午餐。
相關文章:
- 與用零替換現有陣列的值相比,為什么 Numpy 在創建零陣列時要快得多?
- 為什么使用 np.empty 分配不是 O(1)
uj5u.com熱心網友回復:
您可以np.empty()改用:
%timeit dim3 = np.zeros((150,150,1000)); dim3[mask] = very_important_data
%timeit dim3 = np.empty((150, 150, 1000)); dim3[mask] = very_important_data
輸出(在 macOS 12.4 上):
5.3 ms ± 17.4 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
190 μs ± 382 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
注意:如果您設定所有元素,這只會給出相同的結果,請參閱檔案:https ://numpy.org/doc/stable/reference/generated/numpy.empty.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/485069.html
