簡體   English   中英

有沒有辦法使用純 python 為純函數釋放 GIL?

[英]Is there a way to release the GIL for pure functions using pure python?

我想我一定錯過了什么; 這看起來很正確,但我看不到這樣做的方法。

假設您在 Python 中有一個純 function:

from math import sin, cos

def f(t):
    x = 16 * sin(t) ** 3
    y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)
    return (x, y)

是否有一些內置功能或庫提供某種包裝器,可以在函數執行期間釋放 GIL?

在我的腦海中,我在想一些類似的事情

from math import sin, cos
from somelib import pure

@pure
def f(t):
    x = 16 * sin(t) ** 3
    y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)
    return (x, y)

為什么我認為這可能有用?

因為目前只對 I/O 密集型程序有吸引力的多線程,一旦這些功能長時間運行,就會對它們有吸引力。 做類似的事情

from math import sin, cos
from somelib import pure
from asyncio import run, gather, create_task

@pure  # releases GIL for f
async def f(t):
    x = 16 * sin(t) ** 3
    y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
    return (x, y)


async def main():
    step_size = 0.1
    result = await gather(*[create_task(f(t / step_size))
                            for t in range(0, round(10 / step_size))])
    return result

if __name__ == "__main__":
    results = run(main())
    print(results)

當然, multiprocessing提供Pool.map可以做非常相似的事情。 但是,如果 function 返回非原始/復雜類型,則工作人員必須對其進行序列化,並且主進程必須反序列化並創建新的 object,從而創建必要的副本。 對於線程,子線程傳遞一個指針,主線程簡單地獲得 object 的所有權。 更快(更清潔?)。

為了將此與我幾周前遇到的一個實際問題聯系起來:我正在做一個強化學習項目,其中涉及為類似國際象棋的游戲構建人工智能。 為此,我模擬了 AI 與自己對戰> 100,000場比賽; 每次返回板狀態的結果序列( numpy數組)。 生成這些游戲循環運行,我每次都使用這些數據來創建更強大的 AI 版本。 在這里,在主進程中為每個游戲重新創建(“ malloc ”)狀態序列是瓶頸。 我嘗試重用現有對象,由於許多原因,這是一個壞主意,但這並沒有產生太大的改進。

編輯:這個問題與如何並行運行函數不同? ,因為我不只是在尋找並行運行代碼的任何方法(我知道這可以通過多種方式實現,例如通過multiprocessing )。 我正在尋找一種方法讓解釋器知道當這個 function 在並行線程中執行時不會發生任何不好的事情。

有沒有辦法使用純 python 為純函數釋放 GIL?

簡而言之,答案是否定的,因為這些功能在 GIL 運行的層面上並不是純粹的。

GIL serves not just to protect objects from being updated concurrently by Python code, its primary purpose is to prevent the interpreter from performing a data race (which is undefined behavior , ie forbidden in the C memory model) while accessing and updating global and shared data . 這包括 Python 可見的單例,例如NoneTrueFalse ,還包括所有全局變量,例如模塊、共享字典和緩存。 然后是它們的元數據,例如引用計數和類型對象,以及實現內部使用的共享數據。

考慮提供的純 function:

def f(t):
    x = 16 * sin(t) ** 3
    y = 13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)
    return (x, y)

dis工具揭示了解釋器在執行 function 時執行的操作:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (16)
              2 LOAD_GLOBAL              0 (sin)
              4 LOAD_FAST                0 (t)
              6 CALL_FUNCTION            1
              8 LOAD_CONST               2 (3)
             10 BINARY_POWER
             12 BINARY_MULTIPLY
             14 STORE_FAST               1 (x)
             ...

要運行代碼,解釋器必須訪問全局符號sincos才能調用它們。 它訪問整數 2、3、4、5、13 和 16,它們都是緩存的,因此也是全局的。 如果發生錯誤,它會查找異常類以實例化適當的異常。 即使這些全局訪問不修改對象,它們仍然涉及寫入,因為它們必須更新引用計數

在沒有同步的情況下,這些都不能從多個線程安全地完成。 雖然可以修改 Python 解釋器以實現不訪問全局 state 的真正純函數,但它需要對內部進行重大修改,從而影響與現有 Z0D61F8370CAD14D412F80ZB84D1 的兼容性。 最后一點是事實證明移除 GIL 如此困難的主要原因。

暫無
暫無

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

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