【问题标题】:Very slow communication between Python and C++ program with subprocessPython和C++程序与子进程之间的通信非常慢
【发布时间】:2017-05-09 14:10:38
【问题描述】:

我正在尝试通过使用 subprocess Python 模块将一些重复计算外包给 C++ 程序来加速 Python 程序。

为了说明我的问题,我采用了一个简单的 C++ 代码,它返回输入的双精度值。一百万个整数需要 16 秒,看起来很慢。

这是 C++ 程序(double.exe):

#include <iostream>

using namespace std;

int main()
{
    int a;
    bool goon = true;
    while (goon)
    {
        cin >> a;
        cout << 2 * a << endl;
        if (a == 0)
            goon= false;
    }
}

这里是 Python 3 代码:

from time import time
from subprocess import PIPE,Popen

cmd = ["double"]
process = Popen(cmd, stdin=PIPE,stdout=PIPE, bufsize=32,universal_newlines=True, shell=True)

t0 = time()
for i in range(1,int(1e6)):
    print(i, file=process.stdin, flush=True)
    output = int(process.stdout.readline())
dt = time() - t0
print("Time to communicate : %fs" % dt)
print(0,file=process.stdin,flush=True) # close 'double' program

沟通时间:16.029137s

对我而言,它之所以这么慢,只能是Python进程与C++程序之间通过管道进行通信,但我还没有找到如何加速它。使用子进程或其他库加速这种通信的任何解决方案?

我在 Windows 上使用 Python 3.5.2。

【问题讨论】:

  • 为什么不直接将 C++ 代码作为一个像 boost::python 这样的模块公开给 Python?
  • a) 刚开始和关闭一个进程需要相当长的时间 b) 我不确定你的计算在 C++ 中是否实际上会更快。你有分析过吗?
  • 除了模块方法之外,您还可以考虑使用 scipy.weavecffi 之类的方法(可能适合您的用例,也可能不适合)。
  • 我的经验也是 Windows 下的 stdin/stdout 通信非常慢。除了使用标准输入/标准输出之外的其他通信机制或在不同的操作系统下运行之外,我不确定可以做任何事情:(
  • @user45891 我还没有测试过,但据我所知,C/C++ 总是比 Python 快,尤其是对于简单的数字运算(我想在 C++ 上计算的任务就是这种情况) ),这就是为什么我想尝试一下,看看我能获得什么收益。

标签: python c++ subprocess pipe


【解决方案1】:

问题不在于标准输入本身的通信,而在于大规模的上下文切换。您在 C++ 代码中执行了一个非常小的“任务”,但是对于每个这样的“任务”,python 代码应该将数据写入管道、刷新、进入休眠状态,C++ 部分唤醒、解析输入、计算结果、打印输出,冲洗并进入睡眠状态。然后python代码唤醒等。

进入睡眠和唤醒(以及相关的上下文切换)不是免费的。对于“任务”的大小(将输入乘以 2),这种开销会消耗大部分时间。

您可以通过为 C++ 程序批量提供工作或执行更大的任务来“修复”该问题。或两者兼而有之。

例如,如果在每次写入后刷新管道,则使用 10 个数字批次完成的相同作业在我的盒子上运行速度快 2 倍。代码:

for i in range(1,int(1e5)):
    for j in range(1, 10):
        print(i*10 + j, file=process.stdin, flush=True)
    for j in range(1, 10):
        output = int(process.stdout.readline())

如果每 10 个数字只刷新一次,它的运行速度比上一个示例快 1.5 倍(或比原始代码快 3 倍):

for i in range(1,int(1e5)):
    for j in range(1, 10):
        print(i*10 + j, file=process.stdin)
    process.stdin.flush()
    for j in range(1, 10):
        output = int(process.stdout.readline())

如果“任务”更大,那么您必须为上下文切换付出的代价是相同的。但与任务的规模相比,它并没有那么大。例如,让我们假设上下文切换需要 0.1 秒(这在现实生活中要小得多,这只是一个示例)。如果任务是在 1 毫秒内完成的乘法运算(再次举例),那么与任务相比的上下文切换开销为 10000%。但是如果你的任务很重,需要 1s 来执行,那么开销只有 10%。相对值相差1000倍。

【讨论】:

  • 我以一个简单任务的例子作为 double 来说明我的问题,但我想要加速的任务实际上更大,但不幸的是不足以通过名为的 C++ 代码获得实时增益与任务的 Python 实现相比,子进程。在我的电脑上,你的两个代码都在 5 秒内运行,比我的快 3 倍。然而,在我真正的问题中,我需要在计算下一次计算之前得到答案,因此无法完成这样的实现。
  • @Aral 在这种情况下,您可能想要为您的 python 代码实现一个 C/C++ 扩展模块 - 假设您必须做的计算非常繁重并在python会很慢。见docs.python.org/3/extending/extending.html
  • @aral,无论语言如何,在细粒度任务上使用 IPC 总是不利于性能提升,而用于 IPC 的 stdin 是可能的最慢和最差的方法。即使使用扩展,在处理过程中,您仍然会遇到(自动)数据类型转换、堆栈转换等问题。内存映射文件可能会有所帮助,但您应该问问自己,您是否已经用尽了分析和优化 Python 算法的方法。
【解决方案2】:

只是一个猜测,但这可能是因为std::endl 不仅写入了一个换行符,而且还刷新了输出流。冲洗可能是最耗时的部分。因此,如果你只是写它可能会更快

std::cout << 2 * a << "\n"; //Unix style line break

std::cout << 2 * a << "\r\n"; //Windows style line break

(注意:未经测试这是否有效或隐式刷新实际上是否需要存在。)

【讨论】:

  • 我测试过,与std::endl相比,它不会加速通信。即使我在 Windows 上,正确的方法是 Unix 样式的换行符。使用 '\r\n',它为每个发送的整数生成两个输出行:“*doublea*\n”和“\n”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-07
  • 2018-09-16
  • 1970-01-01
  • 2011-01-05
  • 1970-01-01
相关资源
最近更新 更多