【发布时间】:2020-12-04 08:37:02
【问题描述】:
我想我一定是错过了什么;这看起来很正确,但我看不到这样做的方法。
假设你在 Python 中有一个纯函数:
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,它可以做非常相似的事情。但是,如果函数返回非原始/复杂类型,则工作人员必须对其进行序列化,而主进程必须反序列化并创建新对象,从而创建必要的副本。对于线程,子线程传递一个指针,主线程简单地获取对象的所有权。更快(更清洁?)。
为了将此与我几周前遇到的一个实际问题联系起来:我正在做一个强化学习项目,其中涉及为类似国际象棋的游戏构建人工智能。为此,我在模拟 AI 与自己对战 > 100,000 游戏;每次返回板状态的结果序列(numpy 数组)。生成这些游戏循环运行,我每次都使用这些数据来创建更强大的 AI 版本。在这里,在主进程中为每个游戏重新创建(“malloc”)状态序列是瓶颈。我尝试重用现有对象,由于许多原因,这是一个坏主意,但这并没有带来太大的改进。
编辑:这个问题与 How to run functions in parallel? 不同,因为我不只是在寻找并行运行代码的任何方法(我知道这可以通过多种方式实现,例如通过 multiprocessing)。我正在寻找一种方法让解释器知道当这个函数在并行线程中执行时不会发生任何不好的事情。
【问题讨论】:
-
理论上你可以通过编写一个 C 扩展来禁用 GIL。但我无法真正预测其后果。这样你就达到了不安全、未定义的行为。例如,谁知道这些 sin、cos 到底做了什么,以及在没有 GIL 的情况下它们是否能正常工作?这里的核心问题是 Python 很慢,而且它的线程支持很烂。也许你应该改用其他语言?
-
Python 是 Python,你不知道
t是什么。如果是单个浮点数,释放和重新获取 GIL 的成本将高于 5 次三角运算和一些基本数学运算的成本。即使在没有 GIL 问题的 C++ 中,为这样的小任务创建新的future也是没有意义的。当然,两个 AI 之间的国际象棋游戏是另一回事,但你需要 Tensorflow。 GIL 不适用于在 GPU 上运行的代码。 -
这能回答你的问题吗? How to run functions in parallel?
-
@freakish 我同意,不加思索地释放 GIL 就是打开潘多拉魔盒。我想到的第一个“安全”方法是创建一个新范围,该范围无权访问其执行函数的父级,然后丢弃除返回变量之外的范围。不过,我没有想到这一点,尤其是。关于
imports。我认为可能已经有一个图书馆,但我不知道。 -
@MSalters 是的,我已经在使用 Tensorflow 来计算移动了。它并不能解决所有问题,原因如下:这确实是 CPU 工作。 (b) 游戏中的预测是连续的(多亏了高分支因子),(c) 你可以批处理并发运行的游戏,但是在多进程应用程序中批处理它们是痛苦的(当前方法),(d) 允许每个进程共享GPU 浪费了宝贵的 GPU 内存(权重/参数的副本)
标签: python multiprocessing python-asyncio gil