Быстрое преобразование вектора C / C ++ в массив Numpy - PullRequest
5 голосов
/ 24 марта 2011

Я использую SWIG, чтобы склеить некоторый код C ++ с Python (2.6), и часть этого клея включает фрагмент кода, который преобразует большие поля данных (миллионы значений) со стороны C ++ в массив Numpy. Лучший метод, который я могу придумать, реализует итератор для класса, а затем предоставляет метод Python:

def __array__(self, dtype=float):
    return np.fromiter(self, dtype, self.size())

Проблема в том, что каждый вызов итератора next очень дорогой, поскольку он должен проходить через три или четыре оболочки SWIG. Это занимает слишком много времени. Я могу гарантировать, что данные C ++ хранятся смежно (поскольку они живут в std :: vector), и просто кажется, что Numpy должен иметь возможность взять указатель на начало этих данных вместе с количеством значений, которые они содержат, и прочитайте это прямо.

Есть ли способ передать указатель на internal_data_[0] и значение internal_data_.size() на numpy, чтобы он мог напрямую получить доступ или скопировать данные без всех накладных расходов Python?

Ответы [ 4 ]

2 голосов
/ 24 марта 2011

Вы хотите определить __array_interface__() вместо .Это позволит вам вернуть указатель и информацию о форме напрямую.

1 голос
/ 25 марта 2011

Возможно, можно было бы использовать f2py вместо swig.Несмотря на свое название, он способен взаимодействовать как с C, так и с Fortran.См. http://www.scipy.org/Cookbook/f2py_and_NumPy

Преимущество заключается в том, что он автоматически выполняет преобразование в пустые массивы.

Два предостережения: если вы еще не знаете Фортран, вы можете найти f2py немного странным;и я не знаю, насколько хорошо это работает с C ++.

0 голосов
/ 20 апреля 2011

Таким образом, похоже, что единственное реальное решение состоит в том, чтобы создать что-то на pybuffer.i, которое можно скопировать из C ++ в существующий буфер.Если вы добавите это в файл включения SWIG:

%insert("python") %{
import numpy as np
%}

/*! Templated function to copy contents of a container to an allocated memory
 * buffer
 */
%inline %{
//==== ADDED BY numpy.i
#include <algorithm>

template < typename Container_T >
void copy_to_buffer(
        const Container_T& field,
        typename Container_T::value_type* buffer,
        typename Container_T::size_type length
        )
{
//    ValidateUserInput( length == field.size(),
//            "Destination buffer is the wrong size" );
    // put your own assertion here or BAD THINGS CAN HAPPEN

    if (length == field.size()) {
        std::copy( field.begin(), field.end(), buffer );
    }
}
//====

%}

%define TYPEMAP_COPY_TO_BUFFER(CLASS...)
%typemap(in) (CLASS::value_type* buffer, CLASS::size_type length)
(int res = 0, Py_ssize_t size_ = 0, void *buffer_ = 0) {

    res = PyObject_AsWriteBuffer($input, &buffer_, &size_);
    if ( res < 0 ) {
        PyErr_Clear();
        %argument_fail(res, "(CLASS::value_type*, CLASS::size_type length)",
                $symname, $argnum);
    }
    $1 = ($1_ltype) buffer_;
    $2 = ($2_ltype) (size_/sizeof($*1_type));
}
%enddef


%define ADD_NUMPY_ARRAY_INTERFACE(PYVALUE, PYCLASS, CLASS...)

TYPEMAP_COPY_TO_BUFFER(CLASS)

%template(_copy_to_buffer_ ## PYCLASS) copy_to_buffer< CLASS >;

%extend CLASS {
%insert("python") %{
def __array__(self):
    """Enable access to this data as a numpy array"""
    a = np.ndarray( shape=( len(self), ), dtype=PYVALUE )
    _copy_to_buffer_ ## PYCLASS(self, a)
    return a
%}
}

%enddef

, тогда вы можете сделать контейнер «Numpy» доступным с

%template(DumbVectorFloat) DumbVector<double>;
ADD_NUMPY_ARRAY_INTERFACE(float, DumbVectorFloat, DumbVector<double>);

Затем в Python просто выполните:

# dvf is an instance of DumbVectorFloat
import numpy as np
my_numpy_array = np.asarray( dvf )

У этого есть только издержки одного вызова перевода Python <-> C ++, а не N, который будет получен из типичного массива длины N.

Несколько более полная версия этогокод является частью моего проекта PyTRT на github .

0 голосов
/ 24 марта 2011

Если вы поместите вектор в объект, который реализует Python Буферный интерфейс , вы можете передать его в массив numpy для инициализации (см. docs , третий аргумент).Могу поспорить, что эта инициализация намного быстрее, поскольку она может просто использовать memcpy для копирования данных.

...