【问题标题】:Improve speed of passing data from Python to C(++) via ctypes提高通过 ctypes 将数据从 Python 传递到 C(++) 的速度
【发布时间】:2013-04-14 17:15:47
【问题描述】:

我需要针对时间关键型机器人应用程序优化循环中的函数调用。我的脚本在 python 中,它通过 ctypes 与我编写的 C++ 库进行交互,然后调用微控制器库。

瓶颈是向微控制器缓冲区添加位置-速度-时间点。根据我的时间检查,通过 ctypes 调用 C++ 函数大约需要 0.45 秒,而在 C++ 端,被调用函数需要 0.17 秒。我需要以某种方式减少这种差异。

这里是相关的 python 代码,其中数据是一个二维点数组,而库是通过 ctypes 加载的:

data_np = np.vstack([nodes, positions, velocities, times]).transpose().astype(np.long)

data = ((c_long * 4) * N)()
for i in range(N):
    data[i] = (c_long * 4)(*data_np[i])

timer = time()
clibrary.addPvtAll(N, data)
print("clibrary.addPvtAll() call: %f" % (time() - timer))

这里是被调用的 C++ 函数:

void addPvtAll(int N, long data[][4]) {

    clock_t t0, t1;
    t0 = clock();

    for(int i = 0; i < N; i++) {
        unsigned short node = (unsigned short)data[i][0];
        long p = data[i][1];
        long v = data[i][2];
        unsigned char t = (unsigned char)data[i][3];

        VCS_AddPvtValueToIpmBuffer(device(node), node, p, v, t, &errorCode);
    }

    t1 = clock();
    printf("addPvtAll() call: %f \n", (double(t1 - t0) / CLOCKS_PER_SEC));
}

我不是绝对需要使用 ctypes,但我不想每次运行 Python 代码时都必须编译它。

【问题讨论】:

  • 是C++主程序,调用Python代码,还是Python主程序,调用C++代码。如果主程序是 Python,您应该通过扩展库调用 C++,例如 Python/C API、SWIG、PyCXX 或 Boost.Python。您还可以使用 Cython,它可以让您从 Python 调用 C/C++ 代码。
  • 主程序是Python。但是,我不想每次运行 Python 时都必须编译它,理想情况下也不必重写整个 C++ 库。你建议研究哪一个?
  • Boost.Python 和 SWIG 涉及编写包装器,而不是重写代码。 SWIG 有时会生成丑陋的代码,而 Boost.Python 在 64 位平台上会出现问题。如果您确实使用 Boost.Python,请创建一个 32 位 Linux 虚拟机来运行它。SWIG 使用提供的接口文件自动生成一个包装器。 PyCXX 看起来有点奇怪,但很有希望。你最好的选择是 SWIG 和 Boost.Python。它们都不需要重写。

标签: c++ python optimization ctypes


【解决方案1】:

Python 和 C++ 之间的往返成本可能很高,尤其是在使用 ctypes(类似于普通 C/Python 包装器的解释版本)时。

您的目标应该是尽量减少旅行次数,并尽可能在每次旅行中完成工作。

在我看来,您的代码的粒度太细了(即,执行了太多的行程,而每次行程做的工作太少)。

numpy 包可以将其数据直接暴露给 C/C++。这将让您避免昂贵的 Python 对象装箱和拆箱(以及随之而来的内存分配),并且可以让您传递一系列数据点,而不是一次传递一个点。

修改您的 C++ 代码以一次处理多个点,而不是每次调用一次(很像 sqlite3 模块对 executeexecutemany em>)。

【讨论】:

    【解决方案2】:

    这是我的解决方案,它有效地消除了 Python 和 C 之间的测量时间差。感谢 kirbyfan64sos 建议 SWIG 和 Raymond Hettinger 用于 numpy 中的 C 数组。我在 Python 中使用了一个 numpy 数组,它纯粹作为指针发送到 C - 两种语言都访问相同的内存块。

    除了使用gettimeofday() 而不是clock() 之外,C 函数保持不变,这会给出不准确的时间:

    void addPvtFrame(int pvt[6][4]) {
    
        timeval start,stop,result;
        gettimeofday(&start, NULL);
    
        for(int i = 0; i < 6; i++) {
            unsigned short node = (unsigned short)pvt[i][0];
            long p = (long)pvt[i][1];
            long v = (long)pvt[i][2];
            unsigned char t = (unsigned char)pvt[i][3];
    
            VCS_AddPvtValueToIpmBuffer(device(node), node, p, v, t, &errorCode);
        }
    
        gettimeofday(&stop, NULL);
        timersub(&start,&stop,&result);
        printf("Add PVT time in C code: %fs\n", -(result.tv_sec + result.tv_usec/1000000.0));
    }
    

    此外,我安装了 SWIG 并在我的接口文件中包含以下内容:

    %include "numpy.i"
    %init %{
        import_array();
    %}
    
    %apply ( int INPLACE_ARRAY2[ANY][ANY] ) {(int pvt[6][4])}
    

    最后,我的 Python 代码通过 numpy 将 pvt 构造为一个连续数组:

    pvt = np.vstack([nodes, positions, velocities, times])
    pvt = np.ascontiguousarray(pvt.transpose().astype(int))
    
    timer = time()
    xjus.addPvtFrame(pvt)
    print("Add PVT time to C code: %fs" % (time() - timer))
    

    现在在我的机器上测量的时间大约有 %1 的差异。

    【讨论】:

      【解决方案3】:

      你可以使用data_np.data.tobytes():

      data_np = np.vstack([nodes, positions, velocities, times]).transpose().astype(np.long)
      timer = time()
      clibrary.addPvtAll(N, data_np.data.tobytes())
      print("clibrary.addPvtAll() call: %f" % (time() - timer))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-15
        • 1970-01-01
        相关资源
        最近更新 更多