Skip to content

Commit f24bbdc

Browse files
committed
Extend SliceBox into Owner which can hold onto both Box<[T]> and Vec<T>
By providing type erasure for both Box<[T]> and Vec<T> we can avoid having to transform Vec<T> and Array<A, D> into boxed slices which can potentially re-allocate.
1 parent cdcec14 commit f24bbdc

File tree

5 files changed

+139
-93
lines changed

5 files changed

+139
-93
lines changed

src/array.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::{iter::ExactSizeIterator, marker::PhantomData};
1717
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
1818
use crate::dtype::{DataType, Element};
1919
use crate::error::{FromVecError, NotContiguousError, ShapeError};
20-
use crate::slice_box::SliceBox;
20+
use crate::owner::Owner;
2121

2222
/// A safe, static-typed interface for
2323
/// [NumPy ndarray](https://numpy.org/doc/stable/reference/arrays.ndarray.html).
@@ -442,34 +442,39 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
442442
Self::from_owned_ptr(py, ptr)
443443
}
444444

445-
pub(crate) unsafe fn from_boxed_slice<'py, ID>(
445+
pub(crate) unsafe fn from_raw_parts<'py, ID, O>(
446446
py: Python<'py>,
447447
dims: ID,
448448
strides: *const npy_intp,
449-
boxed_slice: Box<[T]>,
450-
data_ptr: Option<*const T>,
449+
data_ptr: *const T,
450+
owner: O,
451451
) -> &'py Self
452452
where
453453
ID: IntoDimension<Dim = D>,
454+
Owner: From<O>,
454455
{
455-
let dims = dims.into_dimension();
456-
let data_ptr = data_ptr.unwrap_or_else(|| boxed_slice.as_ptr());
457-
let container = SliceBox::new(boxed_slice);
458-
let cell = pyo3::PyClassInitializer::from(container)
456+
let owner = pyo3::PyClassInitializer::from(Owner::from(owner))
459457
.create_cell(py)
460458
.expect("Object creation failed.");
459+
460+
let dims = dims.into_dimension();
461461
let ptr = PY_ARRAY_API.PyArray_New(
462462
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
463463
dims.ndim_cint(),
464464
dims.as_dims_ptr(),
465-
T::npy_type() as i32,
466-
strides as *mut _, // strides
467-
data_ptr as _, // data
468-
mem::size_of::<T>() as i32, // itemsize
469-
npyffi::NPY_ARRAY_WRITEABLE, // flag
470-
ptr::null_mut(), // obj
465+
T::npy_type() as c_int,
466+
strides as *mut npy_intp, // strides
467+
data_ptr as *mut c_void, // data
468+
mem::size_of::<T>() as c_int, // itemsize
469+
npyffi::NPY_ARRAY_WRITEABLE, // flag
470+
ptr::null_mut(), // obj
471471
);
472-
PY_ARRAY_API.PyArray_SetBaseObject(ptr as *mut npyffi::PyArrayObject, cell as _);
472+
473+
PY_ARRAY_API.PyArray_SetBaseObject(
474+
ptr as *mut npyffi::PyArrayObject,
475+
owner as *mut ffi::PyObject,
476+
);
477+
473478
Self::from_owned_ptr(py, ptr)
474479
}
475480

src/convert.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,21 @@ impl<T: Element> IntoPyArray for Box<[T]> {
3737
type Item = T;
3838
type Dim = Ix1;
3939
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
40-
let len = self.len();
40+
let dims = [self.len()];
4141
let strides = [mem::size_of::<T>() as npy_intp];
42-
unsafe { PyArray::from_boxed_slice(py, [len], strides.as_ptr(), self, None) }
42+
let data_ptr = self.as_ptr();
43+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
4344
}
4445
}
4546

4647
impl<T: Element> IntoPyArray for Vec<T> {
4748
type Item = T;
4849
type Dim = Ix1;
4950
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
50-
self.into_boxed_slice().into_pyarray(py)
51+
let dims = [self.len()];
52+
let strides = [mem::size_of::<T>() as npy_intp];
53+
let data_ptr = self.as_ptr();
54+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
5155
}
5256
}
5357

@@ -59,21 +63,9 @@ where
5963
type Item = A;
6064
type Dim = D;
6165
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
62-
let (strides, dim) = (self.npy_strides(), self.raw_dim());
63-
let orig_ptr = self.as_ptr();
64-
// Element of which size is 0 is not supported, but check it for future changes
65-
let is_empty_or_size0 = self.is_empty() || std::mem::size_of::<A>() == 0;
66-
let vec = self.into_raw_vec();
67-
let offset = if is_empty_or_size0 {
68-
0
69-
} else {
70-
(orig_ptr as usize - vec.as_ptr() as usize) / std::mem::size_of::<A>()
71-
};
72-
let mut boxed_slice = vec.into_boxed_slice();
73-
// data_ptr is not always the pointer to the 1st element.
74-
// See https://github.com/PyO3/rust-numpy/issues/182 for the detail.
75-
let data_ptr = unsafe { boxed_slice.as_mut_ptr().add(offset) };
76-
unsafe { PyArray::from_boxed_slice(py, dim, strides.as_ptr(), boxed_slice, Some(data_ptr)) }
66+
let (strides, dims) = (self.npy_strides(), self.raw_dim());
67+
let data_ptr = self.as_ptr();
68+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
7769
}
7870
}
7971

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ mod dtype;
4141
mod error;
4242
pub mod npyffi;
4343
pub mod npyiter;
44+
mod owner;
4445
mod readonly;
45-
mod slice_box;
4646
mod sum_products;
4747

4848
pub use ndarray;

src/owner.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::{mem, slice};
2+
3+
use ndarray::{ArrayBase, Dimension, OwnedRepr};
4+
use pyo3::class::impl_::{PyClassImpl, ThreadCheckerStub};
5+
use pyo3::pyclass::PyClass;
6+
use pyo3::pyclass_slots::PyClassDummySlot;
7+
use pyo3::type_object::{LazyStaticType, PyTypeInfo};
8+
use pyo3::{ffi, types::PyAny, PyCell};
9+
10+
use crate::dtype::Element;
11+
12+
pub(crate) struct Owner {
13+
ptr: *mut u8,
14+
len: usize,
15+
cap: usize,
16+
drop: unsafe fn(*mut u8, usize, usize),
17+
}
18+
19+
unsafe impl Send for Owner {}
20+
21+
impl<T: Send> From<Box<[T]>> for Owner {
22+
fn from(data: Box<[T]>) -> Self {
23+
unsafe fn drop_boxed_slice<T>(ptr: *mut u8, len: usize, _cap: usize) {
24+
let _ = Box::from_raw(slice::from_raw_parts_mut(ptr as *mut T, len) as *mut [T]);
25+
}
26+
27+
let ptr = data.as_ptr() as *mut u8;
28+
let len = data.len();
29+
let cap = 0;
30+
let drop = drop_boxed_slice::<T>;
31+
32+
mem::forget(data);
33+
34+
Self {
35+
ptr,
36+
len,
37+
cap,
38+
drop,
39+
}
40+
}
41+
}
42+
43+
impl<T: Send> From<Vec<T>> for Owner {
44+
fn from(data: Vec<T>) -> Self {
45+
unsafe fn drop_vec<T>(ptr: *mut u8, len: usize, cap: usize) {
46+
let _ = Vec::from_raw_parts(ptr as *mut T, len, cap);
47+
}
48+
49+
let ptr = data.as_ptr() as *mut u8;
50+
let len = data.len();
51+
let cap = data.capacity();
52+
let drop = drop_vec::<T>;
53+
54+
mem::forget(data);
55+
56+
Self {
57+
ptr,
58+
len,
59+
cap,
60+
drop,
61+
}
62+
}
63+
}
64+
65+
impl<A, D> From<ArrayBase<OwnedRepr<A>, D>> for Owner
66+
where
67+
A: Element,
68+
D: Dimension,
69+
{
70+
fn from(data: ArrayBase<OwnedRepr<A>, D>) -> Self {
71+
Self::from(data.into_raw_vec())
72+
}
73+
}
74+
75+
impl Drop for Owner {
76+
fn drop(&mut self) {
77+
unsafe {
78+
(self.drop)(self.ptr, self.len, self.cap);
79+
}
80+
}
81+
}
82+
83+
impl PyClass for Owner {
84+
type Dict = PyClassDummySlot;
85+
type WeakRef = PyClassDummySlot;
86+
type BaseNativeType = PyAny;
87+
}
88+
89+
impl PyClassImpl for Owner {
90+
const DOC: &'static str = "Memory store for a PyArray backed by a Rust type \0";
91+
92+
type BaseType = PyAny;
93+
type Layout = PyCell<Self>;
94+
type ThreadChecker = ThreadCheckerStub<Self>;
95+
}
96+
97+
unsafe impl PyTypeInfo for Owner {
98+
type AsRefTarget = PyCell<Self>;
99+
100+
const NAME: &'static str = "Owner";
101+
const MODULE: Option<&'static str> = Some("_rust_numpy");
102+
103+
#[inline]
104+
fn type_object_raw(py: pyo3::Python) -> *mut ffi::PyTypeObject {
105+
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
106+
TYPE_OBJECT.get_or_init::<Self>(py)
107+
}
108+
}

src/slice_box.rs

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)