【问题标题】:Would setting a mutex manually improve performance?手动设置互斥锁会提高性能吗?
【发布时间】:2019-11-04 14:58:03
【问题描述】:

我的 python 程序绝对是 cpu 绑定的,但是 40% 到 55% 的时间是在 z3 求解器中的 C 代码中执行的(它不知道任何针对 gil),其中每次调用 C 函数(@ 987654321@) 需要将近一分钟才能完成(到目前为止parallel_enable 参数仍然导致此函数在单线程模式下工作并阻塞主线程)。

我不能使用多处理,因为 z3_objects 对可序列化不友好(除非这里有人可以证明不是这样)。由于它们是多个任务(每个任务在 dict 中为其他任务添加更多 z3 工作),我最初直接设置了多线程。但是,尽管在求解器中花费了大量时间,但 Gil 对性能的影响肯定大于好处(尤其是超线程)。

但是,如果我在 z3py 模块中手动(通过threading.Lock.aquire())设置了一个阻塞互斥锁,就在从 C 代码切换之后,这将只在所有其他线程都在执行求解器工作时才允许其他线程运行,这是否会删除gil 性能损失(因为它们在执行 python 代码时只有 1 个线程,并且在 z3_optimize_check 之前释放锁之前它总是相同的)?
我的意思是使用 threading.Lock.aquire() 触发调用PyEval_SaveThread() 好像 z3 直接做的一样?

【问题讨论】:

    标签: python python-3.x python-multithreading z3py gil


    【解决方案1】:

    到目前为止parallel_enable参数仍然导致该函数工作在单线程模式并阻塞主线程

    我认为你误解了这一点。 z3 以并行模式运行意味着您从单个 Python 线程调用它,然后它为自己生成多个操作系统级线程,完成工作,清理线程并为您返回结果。它不会奇迹般地让 Python 在没有 GIL 的情况下运行。
    从 Python 的角度来看,它仍然一次只做一件事,而那一件事就是调用 z3。它一直持有 GIL。因此,如果您在计算运行时看到使用了多个 CPU 内核/线程,这就是 z3 的并行模式的影响,内部分支到多个线程。

    还有一件事,释放 GIL,就像阻塞 I/O 操作所做的那样。这不是魔术发生的,有一个呼叫对:

    PyThreadState* PyEval_SaveThread()
    释放全局解释器锁(如果已创建)并将线程状态重置为NULL,返回之前的线程状态(不为 NULL)。如果锁已经被创建,那么当前线程肯定已经获取了它。

    void PyEval_RestoreThread(PyThreadState *tstate)
    获取全局解释器锁(如果已创建)并将线程状态设置为tstate,不能为NULL。如果锁已经被创建,那么当前线程一定没有获得它,否则就会发生死锁。

    这些是 C 调用,因此扩展开发人员可以访问它们。当开发者知道代码会运行很长时间,不需要访问 Python 内部时,可以使用PyEval_SaveThread(),然后 Python 可以继续其他 Python 线程。并且当长时间完成后,线程可以重新自我介绍并使用PyEval_RestoreThread()申请GIL。
    但是,这些事情只有在开发人员让它们发生时才会发生。而对于 z3,情况可能并非如此。

    直接回答您的问题:不,Python 代码不能释放 GIL 并使其保持释放,因为 GIL 是 Python 线程在执行时必须持有的锁。因此,每当 Python 的“指令”返回时,GIL 就会再次保留。


    显然不知何故,我设法不包含我想要的链接,所以它们在页面 https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock 上(链接的段落讨论了我简要总结的内容)。

    Z3是开源的(https://github.com/Z3Prover/z3),源代码既不包含PyEval_SaveThread,也不包含包装快捷方式Py_BEGIN_ALLOW_THREADS字符序列。


    但是,它有一个并行的 Python 示例,顺便说一句。 https://github.com/Z3Prover/z3/blob/master/examples/python/parallel.py,与

    from multiprocessing.pool import ThreadPool
    

    所以我假设它可能会经过测试并与multiprocessing 一起使用。

    【讨论】:

    • I think you are misunderstanding that. z3 running in parallel mode means that you call it from a single Python thread, and then it spawns multiple OS-level threads for itself hemm no.但很可能z3_optimize_check 支持它。即使有更多可用的cpu,它充其量也只会产生自己的线程(因此添加或删除更多内核根本不会改变程序速度,而只分析主线程的cProfile清楚地表明几乎一半的时间花费是在z3_optimize_check 完成,每小时不到 40 个电话)。
    • 我的意思是:使用threading.Lock.aquire() 会触发对PyEval_SaveThread() 的调用吗?内部好像 z3 直接做的一样?
    • @user2284570 只有在执行 Python“指令”时才能释放 GIL。当 Python 线程进入下一条指令时,它必须再次持有 GIL,这就是 GIL 是锁的方式。因此,虽然thread.Lock.acquire() 可能会在必须等待给定的thread.Lock 对象时释放 GIL,但它必须在返回之前取回 GIL,因此给定线程在继续执行时会持有 GIL。
    • 对不起,我还是不明白……我的想法是否允许多线程而不会对最初的愚蠢实现造成性能损失(因为我正在直接修改 z3py,所以在切换之前只有 1 条 python 指令回到 C 代码)。
    • 投反对票。我不是在问 z3 做什么或不做什么:题外话。
    猜你喜欢
    • 2012-08-12
    • 2011-07-08
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多