доступ к несопоставленной памяти в массиве, переданном с Python на C ++

Я показываю класс C ++ Python, используя pybind11.

Он принимает numpy.array в своем конструкторе и получает указатель на свои внутренние данные. (Он не копирует данные).

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <iostream>

namespace py = pybind11;

struct Data
{
    Data(const py::array_t<double, py::array::c_style| py::array::forcecast>& arr)
        : p(arr.data())
    {
        std::cout << "arr=" << p    << std::endl;
        std::cout << "[0]=" << p[0] << std::endl;
    }
    const double* p;
};

У меня есть другой класс, который принимает const Data&, тем самым получая доступ к данным массива.

struct Manager
{
    Manager(const Data& data)
        : data_(data)
    {
        const double* p = data_.p;

        std::cout << "data.arr=" << p    << std::endl;
        std::cout << "data.[0]=" << p[0] << std::endl;
    }
    const Data& data_;
};

Здесь два класса доступны для python с помощью pybind11:

PYBIND11_MODULE(foo, m)
{
    py::class_<Data>(m, "Data")
        .def(py::init<const py::array_t<double, py::array::c_style| py::array::forcecast>&>());

    py::class_<Manager>(m, "Manager")
        .def(py::init<const Data&>());
}

Это хорошо работает. Я могу импортировать свой модуль, создать экземпляр Data из numpy.array, а затем передать его Manager:

>>> import pandas
>>> import numpy
>>> import foo

>>> df = pandas.DataFrame(data = numpy.random.rand(990000, 7))
>>> d = foo.Data(df.values)
>>> c = foo.Manager(d)

Мой скрипт работает нормально, и вы можете видеть, как мой код C ++ обращается к данным numpy.array и выводит его адрес и первый элемент в стандартный вывод:

arr=0x7f47df313010
[0]=0.980507
data.arr=0x7f47df313010
data.[0]=0.980507

Все вышеперечисленное я создал в попытке создать MCVE, чтобы проиллюстрировать проблему, с которой я столкнулся ниже.

Однако теперь я загружаю файл рассола pandas DataFrame, который у меня есть (вот ссылка для загрузки рассматриваемого файла рассола):

>>> import pandas
>>> import foo

>>> df = pandas.read_pickle('data5.pk') 
>>> a = df.values
>>> d = foo.Data(a)
>>> c = foo.Manager(d)

и мой код C ++ дает сбой при попытке доступа к данным массива.

Вот стандартный вывод:

arr=0x7f8864241010
arr[0]=7440.7
data.arr=0x7f8864241010
<dumps core>

Таким образом, указатель на массив такой же в Manager, но попытка разыменования указателя вызывает SEGV.

Запустив его через valgrind, valgrind сообщает Access not within mapped region at address 0x7f8864241010 (то есть: адрес numpy.array).

Python полностью доволен моим файлом рассола:

>>> import pandas

>>> df = pandas.read_pickle('data5.pk')
>>> df.shape
(990000, 7) 
>>> df
                  A             B             C            D            E  \
10000   7440.695240  15055.443905  14585.542158  3647.710616  8139.777981   
10001   7440.607794  15055.356459  14585.454712  3647.623171  8139.690536   
10002   7441.155761  15055.904426  14586.002679  3648.171138  8140.238503   
10003   7440.430209  15055.178874  14585.277127  3647.445585  8139.512950   
10004   7440.418058  15055.166724  14585.264977  3647.433435  8139.500800   
10005   7440.906603  15055.655268  14585.753521  3647.921979  8139.989344   
10006   7440.525167  15055.273832  14585.372085  3647.540543  8139.607908
...

Я не могу понять, что не так с моим файлом рассола.

  • Я пробовал создать numpy.array и травление, это отлично работает
  • Я пробовал создать pandas.DataFrame и травление, это отлично работает
  • Я нарезал свой «недействительный» фрейм данных и могу получить подмножество, которое отлично работает

В моих данных есть что-то, чему питон доволен, но вызывает SEGV в C ++.

Как я могу это диагностировать?


person Steve Lorimer    schedule 13.07.2018    source источник
comment
Почему вы обвиняете рассол?   -  person user2357112 supports Monica    schedule 13.07.2018
comment
@ user2357112 Я виню именно этот файл рассола. Я не могу воспроизвести SEGV с другими данными (например: numpy.random.rand и т. Д.).   -  person Steve Lorimer    schedule 13.07.2018
comment
Кроме того, мой скрипт python точно одинаков во всех отношениях, за исключением одного случая, когда я создаю массив случайных данных, а в другом я читаю данные из файла рассола.   -  person Steve Lorimer    schedule 13.07.2018


Ответы (1)


Рассол в порядке. Это ваш код неправильный. Вы берете указатель на данные массива, ничего не делая, чтобы гарантировать, что эти данные действительно существуют до тех пор, пока объект, который их использует.

Вам необходимо сохранить ссылку на массив и выполнить соответствующее управление счетчиком ссылок. pybind11, вероятно, имеет какой-то механизм для представления ссылки на Python и обработки реф-подсчета за вас. Беглый взгляд на документы выглядит например, ваш код, вероятно, должен принимать array_t по значению вместо ссылки на константу (поскольку array_t уже представляет ссылку на Python) и сохранять его в переменной экземпляра array_t.

person user2357112 supports Monica    schedule 13.07.2018
comment
Конечно, того факта, что у меня есть переменная df, которая поддерживает DataFrame в питоне, достаточно, чтобы предотвратить разрушение массива? Или python читает наперед и знает, что df не используется впоследствии, поэтому он может спекулятивно удалить ресурсы? - person Steve Lorimer; 13.07.2018
comment
Обратите внимание, что я создал несколько других файлов рассола из случайных данных и подмножеств данных проблемы, и все они работают. - person Steve Lorimer; 13.07.2018
comment
@SteveLorimer: DataFrame - это совершенно другой объект. df.values не гарантируется, что он каким-либо образом прикреплен к DataFrame; для DataFrames смешанного типа это будет новый массив. - person user2357112 supports Monica; 13.07.2018
comment
А, понятно, ладно, я сначала попробую захватить ссылку на массив - person Steve Lorimer; 13.07.2018
comment
Я обновил вопрос, чтобы сначала показать захват df.values в переменной, а затем передать это в свой код. К сожалению, результат тот же. Кроме того, я бы подумал, что если бы это было причиной проблемы (время жизни временного), я бы увидел сбой во всех других моих тестовых сценариях использования, но я не могу вообще его сбой, только с этим конкретным рассолом ( даже с другими файлами рассола) - person Steve Lorimer; 13.07.2018
comment
Ой, я только что попробовал твое отредактированное предложение, и оно работает. Большое вам спасибо, я рвал волосы! - person Steve Lorimer; 13.07.2018