Cython中C++函数的性能不佳 - python

我有这个C++函数,可以使用以下代码从Python调用它。与运行纯C++相比,性能只有一半。有没有办法使他们的表现达到相同水平?我用-Ofast -march=native标志编译这两个代码。我不知道我会在哪里损失50%,因为大多数时间应该花在C++内核中。 Cython是否正在制作我可以避免的内存副本?

namespace diff
{
    void diff_cpp(double* __restrict__ at, const double* __restrict__ a, const double visc,
                  const double dxidxi, const double dyidyi, const double dzidzi,
                  const int itot, const int jtot, const int ktot)
    {
        const int ii = 1;
        const int jj = itot;
        const int kk = itot*jtot;

        for (int k=1; k<ktot-1; k++)
            for (int j=1; j<jtot-1; j++)
                for (int i=1; i<itot-1; i++)
                {
                    const int ijk = i + j*jj + k*kk;
                    at[ijk] += visc * (
                            + ( (a[ijk+ii] - a[ijk   ]) 
                              - (a[ijk   ] - a[ijk-ii]) ) * dxidxi 
                            + ( (a[ijk+jj] - a[ijk   ]) 
                              - (a[ijk   ] - a[ijk-jj]) ) * dyidyi
                            + ( (a[ijk+kk] - a[ijk   ]) 
                              - (a[ijk   ] - a[ijk-kk]) ) * dzidzi
                            );
                }
    }
}

我有这个.pyx文件

# import both numpy and the Cython declarations for numpy
import cython
import numpy as np
cimport numpy as np

# declare the interface to the C code
cdef extern from "diff_cpp.cpp" namespace "diff":
    void diff_cpp(double* at, double* a, double visc, double dxidxi, double dyidyi, double dzidzi, int itot, int jtot, int ktot)

@cython.boundscheck(False)
@cython.wraparound(False)
def diff(np.ndarray[double, ndim=3, mode="c"] at not None,
         np.ndarray[double, ndim=3, mode="c"] a not None,
         double visc, double dxidxi, double dyidyi, double dzidzi):
    cdef int ktot, jtot, itot
    ktot, jtot, itot = at.shape[0], at.shape[1], at.shape[2]
    diff_cpp(&at[0,0,0], &a[0,0,0], visc, dxidxi, dyidyi, dzidzi, itot, jtot, ktot)
    return None

我在Python中称这个函数

import numpy as np
import diff
import time

nloop = 20;
itot = 256;
jtot = 256;
ktot = 256;
ncells = itot*jtot*ktot;

at = np.zeros((ktot, jtot, itot))

index = np.arange(ncells)
a = (index/(index+1))**2
a.shape = (ktot, jtot, itot)

# Check results
diff.diff(at, a, 0.1, 0.1, 0.1, 0.1)
print("at={0}".format(at.flatten()[itot*jtot+itot+itot//2]))

# Time the loop
start = time.perf_counter()
for i in range(nloop):
    diff.diff(at, a, 0.1, 0.1, 0.1, 0.1)
end = time.perf_counter()

print("Time/iter: {0} s ({1} iters)".format((end-start)/nloop, nloop))

这是setup.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

import numpy

setup(
    cmdclass = {'build_ext': build_ext},
    ext_modules = [Extension("diff",
                             sources=["diff.pyx"],
                             language="c++",
                             extra_compile_args=["-Ofast -march=native"],
                             include_dirs=[numpy.get_include()])],
)

这里的C++参考文件达到了两倍的性能:

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <stdlib.h>
#include <cstdio>
#include <ctime>
#include "math.h"

void init(double* const __restrict__ a, double* const __restrict__ at, const int ncells)
{
    for (int i=0; i<ncells; ++i)
    {
        a[i]  = pow(i,2)/pow(i+1,2);
        at[i] = 0.;
    }
}

void diff(double* const __restrict__ at, const double* const __restrict__ a, const double visc, 
          const double dxidxi, const double dyidyi, const double dzidzi, 
          const int itot, const int jtot, const int ktot)
{
    const int ii = 1;
    const int jj = itot;
    const int kk = itot*jtot;

    for (int k=1; k<ktot-1; k++)
        for (int j=1; j<jtot-1; j++)
            for (int i=1; i<itot-1; i++)
            {
                const int ijk = i + j*jj + k*kk;
                at[ijk] += visc * (
                        + ( (a[ijk+ii] - a[ijk   ]) 
                          - (a[ijk   ] - a[ijk-ii]) ) * dxidxi 
                        + ( (a[ijk+jj] - a[ijk   ]) 
                          - (a[ijk   ] - a[ijk-jj]) ) * dyidyi
                        + ( (a[ijk+kk] - a[ijk   ]) 
                          - (a[ijk   ] - a[ijk-kk]) ) * dzidzi
                        );
            }
}

int main()
{
    const int nloop = 20;
    const int itot = 256;
    const int jtot = 256;
    const int ktot = 256;
    const int ncells = itot*jtot*ktot;

    double *a  = new double[ncells];
    double *at = new double[ncells];

    init(a, at, ncells);

    // Check results
    diff(at, a, 0.1, 0.1, 0.1, 0.1, itot, jtot, ktot); 
    printf("at=%.20f\n",at[itot*jtot+itot+itot/2]);

    // Time performance 
    std::clock_t start = std::clock(); 

    for (int i=0; i<nloop; ++i)
        diff(at, a, 0.1, 0.1, 0.1, 0.1, itot, jtot, ktot); 

    double duration = (std::clock() - start ) / (double)CLOCKS_PER_SEC;

    printf("time/iter = %f s (%i iters)\n",duration/(double)nloop, nloop);

    return 0;
}

参考方案

这里的问题不是运行期间发生的事情,而是编译期间发生的优化。

完成哪个优化取决于编译器(甚至版本),并且不能保证可以完成的每个优化都会完成。

实际上,cython变慢的原因有两个,这取决于您使用的是g++还是clang++:

由于cython build

中的-fwrapv标志,

  • g++无法优化
  • clang++首先无法优化(请继续阅读以了解发生了什么)。
  • 第一个问题(g++):与纯c ++程序的标志相比,Cython编译时具有不同的标志,因此无法进行某些优化。

    如果查看设置日志,您将看到:

     x86_64-linux-gnu-gcc ... -O2 ..-fwrapv .. -c diff.cpp ... -Ofast -march=native
    

    如您所知,-Ofast将胜过-O2,因为它排在最后。但是问题是-fwrapv,它似乎阻止了一些优化,因为带符号的整数溢出不能被视为UB,并且不再用于优化。

    因此,您有以下选择:

  • -fno-wrapv添加到extra_compile_flags,缺点是现在所有文件都使用已更改的标志进行编译,这可能是不需要的。
  • 从cpp建立一个仅包含您喜欢的标志的库,并将其链接到cython模块。该解决方案有一些开销,但具有健壮的优势:正如您指出的,对于不同的编译器,不同的cython标志可能是问题所在-因此第一个解决方案可能太脆弱了。
  • 不确定您可以禁用默认标志,但是也许文档中有一些信息。
  • 在测试cpp程序中内联的第二期(clang++)

    当我用相当老的5.4版本g++编译您的cpp程序时:

     g++ test.cpp -o test -Ofast -march=native -fwrapv
    

    与没有-fwrapv的编译相比,它慢了将近三倍。但是,这是优化程序的弱点:进行内联时,应该看到没有可能发生带符号整数溢出(所有维都与256有关),因此标记-fwrapv不应该有任何影响。

    我以前的clang++ -version(3.8)似乎在这里做得更好:使用上面的标志,我看不到性能的任何下降。我需要通过-fno-inline禁用内联以使其成为较慢的代码,但是即使没有-fwrapv也是如此,即:

     clang++ test.cpp -o test -Ofast -march=native -fno-inline
    

    因此,系统上倾向于使用c ++程序:内联之后,优化器可以针对已知值优化代码-cython无法做到的事情。

    因此我们可以看到:clang++无法优化任意大小的function diff,但能够针对size = 256对其进行优化。但是,Cython只能使用未经优化的diff版本。这就是为什么-fno-wrapv没有积极影响的原因。

    我的收获:禁止在cpp-tester中内联感兴趣的功能(例如,将其编译到自己的目标文件中),以确保与cython保持平衡;否则,人们会看到为此目的专门优化的程序的性能一个输入。

    注意:有趣的是,如果所有int都替换为unsigned int,那么-fwrapv自然不会发挥任何作用,但是unsigned int的版本与int -version和-fwrapv一样慢,这只是逻辑上的没有可利用的未定义行为。

    扩展Python时可以使用C++功能吗? - c++

    Python手册说您可以在C和C++中为Python创建模块。使用C++时可以利用类和模板之类的东西吗?它不会与其他库和解释器产生不兼容吗? 参考方案 挂钩函数的实现是用C还是用C++实现都没有关系。实际上,我已经看过一些Python扩展,这些扩展有效利用C++模板甚至Boost库。没问题。 :-)

    在Python和C++之间传输数据而无需写入Windows和Unix文件 - python

    我有预先存在的python和C ++文件,其中python文件定义了许多点,而C ++代码利用其现有库进行了所需的计算。最终产品是C ++代码写入的文件。我正在寻找一种在python中获取2000点列表的方法,将其传递给函数,然后执行所有C ++代码并输出我需要的文件。其他注意事项。这必须是可以在Linux或Windows机器上工作的东西,并且最少安装新插件…

    将Python嵌入C++应用程序 - c++

    上下文:我们一直面临的一个持续问题是对我们的市场数据应用程序进行单元测试。这些应用程序坐下来观察从提要中检索到的数据并执行某些操作。一些很难触发的关键事件很少发生,并且测试人员很难在所有情况下验证我们的应用程序是否正常运行,因此我们必须依靠单元测试。这些系统通常通过在事件发生时发出回调(进入我们的应用程序)来工作,然后由我们负责处理此事件。 我设想的解决方案…

    Python的C++名称处理库 - c++

    Improve this question 我想在Python程序中修改和分解C++函数名。有没有类似的东西可用?我搜索了几个小时,也许我很幸运在这里... 参考方案 您很可能不想在Python中执行此操作。顺便说一句,您可能不应该从DLL中导出错误的名称,因为这样会使具有不同编译器的人难以使用。如果必须使用变形的名称,则只需在Python代码中对其进行硬编…

    用于Python和C++应用程序的简单但快速的IPC方法? - c++

    我有一个同时使用Python和C++代码的GNU Radio应用程序。我希望能够表示事件的C++代码。如果它们在同一范围内,我通常会使用一个简单的布尔值,但是代码是分开的,因此需要某种形式的共享内存。有问题的代码对性能至关重要,因此需要一种有效的方法。我最初考虑的是可由Python和C++访问的共享内存段。因此,我可以在python代码中设置一个标志,并从C…