【问题标题】:Cython interfaced with C++: segmentation fault for large arraysCython 与 C++ 接口:大型数组的分段错误
【发布时间】:2016-08-17 03:07:32
【问题描述】:

我正在将我的代码从使用 ctypes 接口的 Python/C 传输到使用 Cython 接口的 Python/C++。新接口将使我更易于维护代码,因为我可以利用所有 C++ 功能并且只需要相对较少的接口代码行。

接口代码与小型数组完美配合。但是,在使用大型数组时会遇到分段错误。我一直在解决这个问题,但还没有接近解决方案。我已经包含了一个发生分段错误的最小示例。请注意,它始终出现在 Linux 和 Mac 上,而且 valgrind 也没有给出见解。另请注意,在纯 C++ 中完全相同的示例确实可以正常工作。

该示例包含(部分)C++ 中的稀疏矩阵类。在 Cython 中创建了一个接口。因此,该类可以从 Python 中使用。

C++ 端

sparse.h

#ifndef SPARSE_H
#define SPARSE_H

#include <iostream>
#include <cstdio>

using namespace std;

class Sparse {

  public:
    int* data;
    int  nnz;

    Sparse();
    ~Sparse();
    Sparse(int* data, int nnz);
    void view(void);

};

#endif

sparse.cpp

#include "sparse.h"

Sparse::Sparse()
{
  data = NULL;
  nnz  = 0   ;
}

Sparse::~Sparse() {}

Sparse::Sparse(int* Data, int NNZ)
{
  nnz  = NNZ ;
  data = Data;
}

void Sparse::view(void)
{

  int i;

  for ( i=0 ; i<nnz ; i++ )
    printf("(%3d) %d\n",i,data[i]);

}

Cython 界面

csparse.pyx

import  numpy as np
cimport numpy as np

# UNCOMMENT TO FIX
#from cpython cimport Py_INCREF

cdef extern from "sparse.h":
  cdef cppclass Sparse:
    Sparse(int*, int) except +
    int* data
    int  nnz
    void view()


cdef class PySparse:

  cdef Sparse *ptr

  def __cinit__(self,**kwargs):

    cdef np.ndarray[np.int32_t, ndim=1, mode="c"] data

    data = kwargs['data'].astype(np.int32)

    # UNCOMMENT TO FIX
    #Py_INCREF(data)

    self.ptr = new Sparse(
      <int*> data.data if data is not None else NULL,
      data.shape[0],
    )

  def __dealloc__(self):
    del self.ptr

  def view(self):
    self.ptr.view()

setup.py

from distutils.core import setup, Extension
from Cython.Build   import cythonize

setup(ext_modules = cythonize(Extension(
  "csparse",
  sources=["csparse.pyx", "sparse.cpp"],
  language="c++",
)))

Python 端

import numpy as np
import csparse

data = np.arange(100000,dtype='int32')

matrix = csparse.PySparse(
  data = data
)

matrix.view() # --> segmentation fault

运行:

$ python setup.py build_ext --inplace
$ python example.py

请注意,data = np.arange(100,dtype='int32') 确实有效

【问题讨论】:

  • 代码太多了。你的minimal reproducible example呢?
  • OK“轨道上的轻量化竞赛”,你说得对!我已经剥离了所有不必要的东西
  • 一种解决方案似乎是增加对数组的引用数量(在csparse.pyx 中添加为cmets)。但是,我认为我已经有效地破坏了 Python 的部分功能......
  • 也许可以,但您必须记住,您的 Python 只是一个基本用 C++ 编写的程序的接口。

标签: python c++ arrays numpy cython


【解决方案1】:

内存由您的 numpy 数组管理。一旦它们超出范围(很可能在 PySparse 构造函数的末尾),数组就不再存在,并且所有指针都无效。这适用于大数组和小数组,但大概你只是幸运地使用了小数组。

您需要保留对您在 PySparse 对象的生命周期中使用的所有 numpy 数组的引用:

cdef class PySparse:

  # ----------------------------------------------------------------------------

  cdef Sparse *ptr
  cdef object _held_reference # added

  # ----------------------------------------------------------------------------

  def __cinit__(self,**kwargs):
      # ....
      # your constructor code code goes here, unchanged...
      # ....

      self._held_reference = [data] # add any other numpy arrays you use to this list

通常,每当您处理 C/C++ 指针时,您都需要认真考虑谁拥有什么,这与普通的 Python 方法相比是一个很大的变化。从 numpy 数组中获取指针不会复制数据,并且它不会向 numpy 提供任何您仍在使用数据的指示。


编辑说明:在我的原始版本中,我尝试使用locals() 作为一种快速收集我想要保留的所有数组的方法。不幸的是,这似乎不包括 cdefed 数组,因此它无法保留您实际使用的数组(请注意,astype() 会复制一份,除非您另有说明,因此您需要保留对副本的引用,而不是作为参数传入的原始文件)。

【讨论】:

  • 感谢您的回复!这个想法让我想到了。但是,我希望 Cython 能够增加和减少对 Python/NumPy 变量的引用数量。据我了解,这是 Cython 相对于自己编写 API 的一大优势。不幸的是,这个谜没有解决......分段错误仍然存​​在。
  • 不幸的是,您越接近 C,Cython 可以为您做的事情就越少(在避免内存管理方面)。它无法知道您是在 Sparse 构造函数中使用过一次指针(这本来是安全的)还是保留它们以备将来使用,这是危险的。请参阅第二期的编辑。
  • 再次感谢。但是,我很确定您的第二次编辑只是部分正确。 int(和int*)类型在大多数体系结构上是 32 位的,也适用于 64 位编译器。只有 long int(和 long long int)类型是 64 位的。有很多关于此的帖子(例如ibm.com/developerworks/library/l-port64)。在我的架构上,int 肯定是 32 位的。
  • 你说得对ints - 我的错误(我已经删除了那个答案)!稍后我会尝试再看看。
  • @Tom 第三次幸运(见编辑)。我已经找到了问题的根源,但是 locals() 并没有完全按照我的想法去做。
猜你喜欢
  • 2021-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 2015-01-21
  • 2019-03-13
相关资源
最近更新 更多