Cython как скопировать из буфера numpy в структуру C с помощью memcpy?

#numpy #cython #memcpy

#numpy #cython #memcpy

Вопрос:

Этот пример компилируется и запускается в Jupyter notebook и демонстрирует проблему.

У меня возникла проблема с этой строкой,

     memcpy(line.points, <point_t *> temp.data, sizeof(point_dtype) * n)
 

который, я ожидаю, копирует буфер из массива numpy temp в points буфер в структуре C. line

Я ожидаю, что значение points будет равно 1.0, но это ненужная, в основном неинициализированная память. Что я делаю не так !?

 %%cython -a

import numpy as np
cimport numpy as cnp
from libc.stdlib cimport malloc
from libc.string cimport memcpy

cdef struct point:
    cnp.float64_t x
    cnp.float64_t y
ctypedef point point_t


cdef struct line:
    point_t * points
ctypedef line line_t


point_dtype = np.dtype([
    ("x", np.float64),
    ("y", np.float64)
])

cdef line_t * make_line():
    """ Make a line of 3 points. Internally does the creation using Numpy 
        and memcpy the result to the line_t C struct.
    """

    # The number of points in the array
    n = 3
    
    cdef cnp.ndarray points = np.empty(n, dtype=point_dtype)
    points["x"] = 0.0
    points["y"] = 0.0

    # Dynamically allocate a line C struct
    line = <line_t*> malloc( sizeof(line_t) )

    # Dynamically allocate space for "n" points
    line.points = <point_t*> malloc( sizeof(point_t) * n)

    # In this toy example we will modify "points" in a temporary array
    # this is closer to what I'm trying to achieve in my actual code.
    temp = np.empty(n, dtype=point_dtype)
    temp[:] = points[:]
    temp["x"]  = 1.0
    temp["y"]  = 1.0

    # Memory copy from the array's buffer into the struct
    memcpy(line.points, <point_t *> temp.data, sizeof(point_dtype) * n)

    print(line.points[0])
    # {'x': 5e-324, 'y': 4.6451830626356e-310}
    # 
    # !!!! Expected !!!!
    # {'x': 1.0, 'y': 1.0}
    
    # Assert fails
    assert line.points[0].x == 1.0
    assert line.points[0].y == 1.0


def test_create_line():
    make_line()

 

Ответ №1:

Фактическая ошибка:

 cdef cnp.ndarray temp = np.empty(n, dtype=point_dtype)
 

без cdef cnp.ndarray then temp.data есть какой-то объект Python (не уверен, что именно), который вы затем передаете как a point_t* , и поэтому копирование из него завершается неудачей.

Я думаю, в принципе, вы должны определить Point as cdef packed struct Point , поскольку я думаю, что данные Numpy упакованы внутри. В данном случае я не думаю, что это имеет значение.

Возможно, было бы лучше использовать memoryviews, и тогда вы сможете утверждать непрерывность ваших массивов.

 cdef point_t[::1] temp_view = temp

# Memory copy from the array's buffer into the struct
memcpy(line.points, amp;temp_view[0], sizeof(point_dtype) * n)
 

В этом случае я бы не стал утруждать себя вводом temp текста, points потому что в этом действительно нет никаких преимуществ. Хорошая особенность подхода memoryview заключается в том, что он позволяет избежать приведения (а приведение помогло замаскировать вашу реальную ошибку) и включает в себя некоторые проверки правильности ваших предположений о размере / расположении данных и смежности.

Комментарии:

1. Большое вам спасибо! Я был в тупике в течение нескольких часов с этим. Два быстрых вопроса: 1) не могли бы вы объяснить amp;temp_view[0] часть, это «взять адрес первого значения в представлении памяти» , 2) Я только что проверил документы cython, но, похоже, при компиляции нет опций, которые помогли бы уловить это, что-то вроде, т.Е. Cythonопция типа «требовать типы»

2. 1) это действительно «взять адрес первого значения в представлении памяти». 2) Я не думаю, что есть опция компилятора, требующая ввода всего. Часть проблемы заключается в том, что каждый раз, когда вы используете <> приведение, вы говорите «я знаю лучше», и если вы ошибаетесь, тогда все ставки отменяются.