Pybind11 и std :: vector - Как освободить данные с помощью капсул? - PullRequest
0 голосов
/ 26 февраля 2019

У меня есть функция C ++, которая возвращает std::vector, и, используя Pybind11, я хотел бы вернуть содержимое этого вектора в виде массива Numpy без необходимости копировать базовые данные вектора в массив необработанных данных.

Текущая попытка

В этот хорошо написанный SO-ответ автор демонстрирует, как гарантировать, что массив необработанных данных, созданный в C ++, соответствующим образом освобождается, когдамассив Numpy имеет нулевое количество ссылок.Я попытался написать версию этого, используя std::vector вместо этого:

// aside - I made a templated version of the wrapper with which
// I create specific instances of in the PYBIND11_MODULE definitions:
//
//     m.def("my_func", &wrapper<int>, ...)
//     m.def("my_func", &wrapper<float>, ...)
// 
template <typename T>
py::array_t<T> wrapper(py::array_t<T> input) {
    auto proxy = input.template unchecked<1>();
    std::vector<T> result = compute_something_returns_vector(proxy);

    // give memory cleanup responsibility to the Numpy array
    py::capsule free_when_done(result.data(), [](void *f) {
        auto foo = reinterpret_cast<T  *>(f);
        delete[] foo;
    });

    return py::array_t<T>({result.size()}, // shape
                          {sizeof(T)},     // stride
                          result.data(),   // data pointer
                          free_when_done);
}

Наблюдаемые проблемы

Однако, если я вызываю это из Python, я наблюдаю две вещи:(1) данные в выходном массиве являются мусором и (2) когда я вручную удаляю массив Numpy, я получаю следующую ошибку (SIGABRT):

python3(91198,0x7fff9f2c73c0) malloc: *** error for object 0x7f8816561550: pointer being freed was not allocated

Я предполагаю, что эта проблема связана сстрока "delete[] foo", которая предположительно вызывается с foo, установленным на result.data().Это не способ освободить std::vector.

Возможные решения

Одним из возможных решений является создание T *ptr = new T[result.size()] и копирование содержимого resultв этот массив необработанных данных.Однако у меня есть случаи, когда результаты могут быть большими, и я хочу не тратить все это время на выделение и копирование.(Но, возможно, это не так долго, как я думаю.)

Кроме того, я немного знаю о std::allocator, но, возможно, есть способ выделить необработанные данныемассив, необходимый выходному вектору вне вызова функции compute_something_returns_vector(), а затем отбрасывать std::vector впоследствии, сохраняя базовый массив необработанных данных?

Последний вариант - переписать compute_something_returns_vector.

1 Ответ

0 голосов
/ 26 февраля 2019

После автономного обсуждения с коллегой я решил свою проблему.Я не хочу совершать ТАК ложную ошибку, поэтому я не приму мой собственный ответ.Тем не менее, ради использования SO в качестве каталога информации я хочу дать здесь ответ для других.

Проблема была проста: result был распределен в стеке и нуждался вбыть распределенным в куче, чтобы free_when_done мог стать владельцем.Ниже приведен пример исправления:

{
    // ... snip ...

    std::vector<T> *result = new std::vector<T>(compute_something_returns_vector(proxy));

    py::capsule free_when_done(result, [](void *f) {
      auto foo = reinterpret_cast<std::vector<T> *>(f);
      delete foo;
    });

    return py::array_t<T>({result->size()}, // shape
                          {sizeof(T)},      // stride
                          result->data(),   // data pointer
                          free_when_done);
}

Я также смог реализовать решение с использованием std::unique_ptr, которое не требует использования функции free_when_done.Однако я не смог запустить Valgrind ни с одним решением, поэтому я не уверен на 100%, что память, содержащаяся в векторе, была должным образом освобождена.(Valgrind + Python для меня загадка.) Для полноты изложения ниже приведен std::unique_ptr подход:

{
    // ... snip ...

    std::unique_ptr<std::vector<T>> result =
        std::make_unique<std::vector<T>>(compute_something_returns_vector(proxy));

    return py::array_t<T>({result->size()}, // shape
                          {sizeof(T)},      // stride
                          result->data());  // data pointer
}

Однако я смог проверить адреса векторов, распределенных как в Python, так и в Python.C ++ и подтвердил, что копии вывода compute_something_returns_vector() не были сделаны.

...