-
-
Notifications
You must be signed in to change notification settings - Fork 11k
ENH: support no-copy pickling for any array that can be transposed to a C-contiguous array #28105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
0dcde49
a27f5eb
50be134
7cea83c
bf5ecaa
a566660
63da2b9
9494672
6b9421a
6ca72f1
0f70349
c045bd9
aa357a9
aee895b
1b2f9d6
4a16cd3
449ea59
f0d528a
a8cc7fc
5f88eb3
4ba4c55
de8899a
4be8c0c
e8ddf99
3ee9178
4783c41
d2a8306
94a297a
e17acbc
7fd5037
16e7067
7982184
d635e65
aaa9b9f
ecb863f
bfbde0e
35ea29f
596db8c
1a82f4e
80ba96d
792468a
c213b37
3174d5e
43e214a
42e1c99
f71549f
d5c2215
8b981dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -1866,6 +1866,7 @@ array_reduce_ex_picklebuffer(PyArrayObject *self, int protocol) | |||
PyObject *picklebuf_args = NULL; | ||||
PyObject *buffer = NULL, *transposed_array = NULL; | ||||
PyArray_Descr *descr = NULL; | ||||
PyObject *rev_perm = NULL; // only used in 'K' order | ||||
char order; | ||||
|
||||
descr = PyArray_DESCR(self); | ||||
|
@@ -1882,19 +1883,46 @@ array_reduce_ex_picklebuffer(PyArrayObject *self, int protocol) | |||
} | ||||
|
||||
/* Construct a PickleBuffer of the array */ | ||||
|
||||
if (!PyArray_IS_C_CONTIGUOUS((PyArrayObject*) self) && | ||||
PyArray_IS_F_CONTIGUOUS((PyArrayObject*) self)) { | ||||
if (PyArray_IS_C_CONTIGUOUS((PyArrayObject *)self)) { | ||||
order = 'C'; | ||||
picklebuf_args = Py_BuildValue("(O)", self); | ||||
rev_perm = Py_BuildValue("O", Py_None); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get it, you mean construct an empty tuple instead of None to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean I would avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied as you suggested. |
||||
} | ||||
else if (PyArray_IS_F_CONTIGUOUS((PyArrayObject *)self)) { | ||||
/* if the array if Fortran-contiguous and not C-contiguous, | ||||
* the PickleBuffer instance will hold a view on the transpose | ||||
* of the initial array, that is C-contiguous. */ | ||||
order = 'F'; | ||||
transposed_array = PyArray_Transpose((PyArrayObject*)self, NULL); | ||||
transposed_array = PyArray_Transpose((PyArrayObject *)self, NULL); | ||||
ngoldbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
picklebuf_args = Py_BuildValue("(N)", transposed_array); | ||||
rev_perm = Py_BuildValue("O", Py_None); | ||||
} | ||||
else { | ||||
order = 'C'; | ||||
picklebuf_args = Py_BuildValue("(O)", self); | ||||
order = 'K'; // won't be used | ||||
IndifferentArea marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
const int n = PyArray_NDIM(self); | ||||
npy_stride_sort_item items[NPY_MAXDIMS]; | ||||
// sort (strde, perm) as descending = transpose to C | ||||
PyArray_CreateSortedStridePerm(n, PyArray_STRIDES(self), items); | ||||
rev_perm = PyTuple_New(n); | ||||
seberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
if(rev_perm == NULL) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
return NULL; | ||||
} | ||||
PyArray_Dims perm; | ||||
npy_intp d[NPY_MAXDIMS]; | ||||
IndifferentArea marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
for (int i = 0; i < n; i++) { | ||||
d[i] = items[i].perm; | ||||
PyTuple_SET_ITEM(rev_perm, items[i].perm, PyLong_FromLong(i)); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit annoying since it can't actually fail in CPython. But maybe we should just add an error check on |
||||
} | ||||
perm.ptr = d; | ||||
perm.len = n; | ||||
transposed_array = PyArray_Transpose((PyArrayObject *)self, &perm); | ||||
ngoldbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
if (!PyArray_IS_C_CONTIGUOUS((PyArrayObject *)transposed_array)) { | ||||
// self is non-contiguous | ||||
Py_DECREF(picklebuf_class); | ||||
Py_DECREF(rev_perm); | ||||
return array_reduce_ex_regular(self, protocol); | ||||
} | ||||
picklebuf_args = Py_BuildValue("(N)", transposed_array); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to ignore, Even better: if we cache There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not familiar with these APIs but I tried.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, |
||||
} | ||||
if (picklebuf_args == NULL) { | ||||
Py_DECREF(picklebuf_class); | ||||
|
@@ -1928,10 +1956,15 @@ array_reduce_ex_picklebuffer(PyArrayObject *self, int protocol) | |||
return NULL; | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thx, added |
||||
|
||||
return Py_BuildValue("N(NONN)", | ||||
from_buffer_func, buffer, (PyObject *)descr, | ||||
PyObject_GetAttrString((PyObject *)self, "shape"), | ||||
PyUnicode_FromStringAndSize(&order, 1)); | ||||
|
||||
PyObject* shape = NULL; | ||||
if(order == 'K') { | ||||
IndifferentArea marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||
shape = PyObject_GetAttrString((PyObject *)transposed_array, "shape"); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Old messy code, so you can ignore: but we have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thx, applied |
||||
} else { | ||||
shape = PyObject_GetAttrString((PyObject *)self, "shape"); | ||||
} | ||||
return Py_BuildValue("N(NONNO)", from_buffer_func, buffer, (PyObject *)descr, shape, | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't right, you lose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you mean lose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leak? You need to decref it at some point or use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. got it, thanks. never wrote on cpython api before, sorry for these oblivious problems. |
||||
PyUnicode_FromStringAndSize(&order, 1), rev_perm); | ||||
} | ||||
|
||||
static PyObject * | ||||
|
@@ -1946,8 +1979,6 @@ array_reduce_ex(PyArrayObject *self, PyObject *args) | |||
|
||||
descr = PyArray_DESCR(self); | ||||
if ((protocol < 5) || | ||||
(!PyArray_IS_C_CONTIGUOUS((PyArrayObject*)self) && | ||||
!PyArray_IS_F_CONTIGUOUS((PyArrayObject*)self)) || | ||||
ngoldbaum marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
PyDataType_FLAGCHK(descr, NPY_ITEM_HASOBJECT) || | ||||
(PyType_IsSubtype(((PyObject*)self)->ob_type, &PyArray_Type) && | ||||
((PyObject*)self)->ob_type != &PyArray_Type) || | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4389,6 +4389,31 @@ def test_f_contiguous_array(self): | |
buffers=buffers) | ||
|
||
assert_equal(f_contiguous_array, depickled_f_contiguous_array) | ||
|
||
def test_transposed_contiguous_array(self): | ||
transposed_contiguous_array = np.random.rand(2, 3, 4).transpose((1, 0, 2)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The transpose example is not chosen carefully, maybe you should just have multiple examples with
so I don't know from the test if the back-transpose is filled correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added more test case |
||
buffers = [] | ||
|
||
# When using pickle protocol 5, Fortran-contiguous arrays can be | ||
# serialized using out-of-band buffers | ||
bytes_string = pickle.dumps(transposed_contiguous_array, protocol=5, | ||
buffer_callback=buffers.append) | ||
|
||
assert len(buffers) > 0 | ||
|
||
depickled_transposed_contiguous_array = pickle.loads(bytes_string, | ||
buffers=buffers) | ||
|
||
assert_equal(transposed_contiguous_array, depickled_transposed_contiguous_array) | ||
|
||
def test_load_legacy_pkl(self): | ||
IndifferentArea marked this conversation as resolved.
Show resolved
Hide resolved
|
||
x = np.arange(24).reshape(3, 4, 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two nits:
I don't understand why the linter isn't failing. Maybe add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
# generated by same code in 2.2.1 | ||
data_dir = os.path.join(os.path.dirname(__file__), 'data') | ||
legacy_pkl_path = os.path.join(data_dir, "legacy_contiguous.pkl") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, this works (I suspect you could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used byte str in code and added more examples |
||
with open(legacy_pkl_path, 'rb') as f: | ||
y = pickle.load(f) | ||
assert_allclose(x, y) | ||
|
||
def test_non_contiguous_array(self): | ||
non_contiguous_array = np.arange(12).reshape(3, 4)[:, :2] | ||
|
Uh oh!
There was an error while loading. Please reload this page.