【问题标题】:Timing of a C++ function wrapped with cython用 cython 包装的 C++ 函数的时序
【发布时间】:2018-05-29 15:09:58
【问题描述】:

我真的不知道怎么问这个问题,但我会尽量说清楚。

我正在计时从 python 调用 C++ 函数。 C++ 函数用 cython 包装。 我目前正在计时 cython 函数的 python 调用,我用time.time() 得到了 52.9 毫秒。另一方面,我正在使用 C++ std::chrono::high_resolution_clock 库为整个 C++ 函数计时。

问题是,我在 C++ 中测量了 17.1 毫秒。

C++ 函数是这样声明的 vector<float> cppfunc(vector<float> array, int a, int b, int c); 并且是一个 A 类方法。

cython 代码只调用 C++ 类方法。该向量包含大约 320k 个元素。

我想知道这两个测量的时间是否可以这样比较? 如果可以,如何解释这种差距? 如果没有,我应该使用哪种计时工具?

Edit1:(cmets 中的链接)两个计时库对于我的用例来说都足够精确(我的拱门上的 cpp 为 10e-9,python 为 10e-6)。

Edit2:添加了简化代码来说明我的观点。使用此代码,python 调用持续时间(~210ms)是实习生 cpp 持续时间(~28ms)的 8 倍。

// example.cpp
#include "example.h"
#include <iostream>
#include <chrono>

std::vector<float> wrapped_function(std::vector<float> array)
{
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<float> result;
    for (int i = 0; i < (int) array.size(); i++) {
        result.push_back(array[i] / 1.1);
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float> duration = end - start;
    printf("Within duration: %.5f\n", duration.count());
    return result;
}


// example.h
#ifndef __EXAMPLE_H_
#define __EXAMPLE_H_
#include <vector>
std::vector<float> wrapped_function(std::vector<float> array);
#endif


# example_wrapper.pxd
from libcpp.vector cimport vector
cdef extern from "example.h":
    vector[float] wrapped_function(vector[float])


# example_wrapper.pyx
from example_wrapper cimport wrapped_function
def cython_wrap(array):
    return wrapped_function(array)


# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
setup(
      cmdclass = {"build_ext": build_ext},
      ext_modules = [
            Extension(name="example_wrapper",
                      sources=["example_wrapper.pyx", "example.cpp"],
                      include_dirs=["/home/SO/"],
                      language="c++",
                      extra_compile_args=["-O3", "-Wall", "-std=c++11"]
                      )
            ]
)


# test.py
import example_wrapper
from time import time

array = [i for i in range(1000000)]
t0 = time()
result = example_wrapper.cython_wrap(array)
t1 = time()
print("Wrapped duration: {}".format(t1 - t0))

【问题讨论】:

  • 您应该调查每种计时方法的精确度。时钟的粒度可达几十毫秒。
  • 你的 C++ 时间是否包括大向量参数的复制?或者你只是在函数本身内计时?
  • 我只是在函数内部计时,既然你这么说,它可能与数组的大小高度相关。
  • 根据thisthis 的帖子,这两种计时方法对我的应用程序都具有显着的精度。所以我唯一没有测量过的是大量参数的副本。关于将 numpy 数组提供给 cpp 函数的更好方法的任何提示?
  • 我首先认为这个问题必须是理论上的,我正在寻找一般建议,但你是对的,所以我添加了一个示例源代码。很抱歉给您带来不便。

标签: c++ python-3.x time cython wrapper


【解决方案1】:

显然区别在于 cython 开销,但为什么它这么大?

包装函数的调用比想象中复杂:

def cython_wrap(array):
       return wrapped_function(array)

array 是一个整数列表,wrapped_function 需要浮点向量,因此 cython 会自动创建一个向量并使用列表中的值填充它。

wrapped_function 返回一个浮点向量,但为了被 python 使用,它必须转换为 python-list。 cython 再次自动创建一个 python-list 并用 python-floats 填充它,这些 python-floats 的构建成本很高,并且对应于返回向量中的浮点数。

如您所见,正在进行大量复制,这解释了您观察到的开销。

Here 是 cython 在从 c++ 容器转换为 python 时自动应用的规则集。

另一个问题:您通过值传递向量array,因此必须复制它。您的 c++ 代码时间不包括这种复制,因此有点不公平。

您应该通过 const-reference 传递向量,即

... wrapped_function(const std::vector<float> &array)

还有一件事:您返回一个可能也将被复制的向量,并且此复制时间再次不包含在您的 c++ 计时中。然而,所有现代编译器都应用了返回值优化,所以这不是问题。

【讨论】:

  • 我听从了您的明智建议,由于 const 参考,我得到了 5% 到 8% 的加速。这是你的想法吗?因为我觉得它有点令人失望,所以我期待 20% 到 30%...
  • 您的问题是“什么解释了差距?”而不是“如何加快速度?” - 解释就是这个答案所提供的。要么cpp部分计算太多,python-cpp转换无所谓,要么你必须避免转换。
猜你喜欢
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
  • 2014-06-05
  • 2021-04-27
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多