0

我有一个用 C 语言编写的多线程应用程序,并在单个 Python 解释器上使用 Python C-API。每个进程接收数据,将其转换为 PyObjects,运行 Python 函数并将结果转换回 C 类型,如以下伪代码:

run_py_function(data) {
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    /* Run C -> PyObject conversions */
    PyObject *pArgs = ...


    /* Call the Python function */
    PyObject *pRet = PyObject_CallObject(pFunc, pArgs);

    /* Do output conversions */
    ret = ...

    PyGILState_Release(gstate);
    return ret;
}

该函数被多个线程调用。使用这种多线程,我不希望由于 GIL 而加速(它更像是对周围代码的要求),但是我希望性能类似于单线程执行。

在一台机器上确实是这种情况,但在另一台机器上肯定不是这样,在不同的机器上,执行速度明显变慢,与线程数无关(但 > 1)。围绕基本功能的步骤,我介绍了一些thread-local profiling,包括等待GIL、输入转换、执行和输出转换。运行时在所有函数调用中累积。结果如下:

机器1,单线程:

线程0:等待:0.000347s Incon:0.088142s Exec:2.729928s Outcon:0.040163s

机器1,多线程:

线程0:等待:0.175036s Incon:0.025976s Exec:0.715943s Outcon:0.011034s
(其余3个线程类似)

机器2,单线程:

线程0:等待:0.002864s Incon:0.120592s Exec:4.243616s Outcon:0.051625s

机器2,多线程:

线程0:等待:0.016123s Incon:0.115358s Exec:3.010255s Outcon:0.043184s
(其余3个线程类似)

这些机器有足够的物理内核。机器 1 运行 Python 3.6.9,机器 2 运行 3.6.12,但除了版本之间的安全修复之外,我没有看到任何重大变化。负载在线程之间均匀分布。在机器 1 上,4 个正在运行的线程中的每个线程都有 1/4 的运行时间。由于 GIL,线程按顺序运行,因此运行时几乎等于单线程运行时。但是,在机器 2 上,每个线程的运行时间都比单线程运行时间的 1/4 多得多,这导致整体运行时间要大得多。

问题:什么可能会限制这里的可扩展性?

我最初认为 Python 可能会在这里执行线程切换,保存/重新加载线程状态可能是一个问题,但是在 Python 函数中设置切换间隔并没有帮助。此外,我尝试在获取 GIL 之前锁定互斥锁,并在释放 GIL 后释放以确保线程不会被切换,但这也无济于事。

4

0 回答 0