Skip to content

Commit 9938eb9

Browse files
authored
Merge pull request #281 from kngwyu/list
Use linked list in ReleasePool
2 parents 473635b + 119e0ab commit 9938eb9

File tree

3 files changed

+95
-34
lines changed

3 files changed

+95
-34
lines changed

benches/bench_dict.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use pyo3::{prelude::*, types::IntoPyDict};
99
fn iter_dict(b: &mut Bencher) {
1010
let gil = Python::acquire_gil();
1111
let py = gil.python();
12-
const LEN: usize = 1_000_000;
12+
const LEN: usize = 1_000_00;
1313
let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py);
1414
let mut sum = 0;
1515
b.iter(|| {

src/pythonrun.rs

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ impl Drop for GILGuard {
108108

109109
/// Release pool
110110
struct ReleasePool {
111-
owned: Vec<NonNull<ffi::PyObject>>,
112-
borrowed: Vec<NonNull<ffi::PyObject>>,
111+
owned: ArrayList<NonNull<ffi::PyObject>>,
112+
borrowed: ArrayList<NonNull<ffi::PyObject>>,
113113
pointers: *mut Vec<NonNull<ffi::PyObject>>,
114114
obj: Vec<Box<any::Any>>,
115115
p: spin::Mutex<*mut Vec<NonNull<ffi::PyObject>>>,
@@ -118,8 +118,8 @@ struct ReleasePool {
118118
impl ReleasePool {
119119
fn new() -> ReleasePool {
120120
ReleasePool {
121-
owned: Vec::with_capacity(256),
122-
borrowed: Vec::with_capacity(256),
121+
owned: ArrayList::new(),
122+
borrowed: ArrayList::new(),
123123
pointers: Box::into_raw(Box::new(Vec::with_capacity(256))),
124124
obj: Vec::with_capacity(8),
125125
p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(256)))),
@@ -128,39 +128,30 @@ impl ReleasePool {
128128

129129
unsafe fn release_pointers(&mut self) {
130130
let mut v = self.p.lock();
131-
132-
// vec of pointers
133-
let ptr = *v;
134-
let vec: &'static mut Vec<_> = &mut *ptr;
131+
let vec = &mut **v;
135132
if vec.is_empty() {
136133
return;
137134
}
138135

139136
// switch vectors
140-
*v = self.pointers;
141-
self.pointers = ptr;
137+
std::mem::swap(&mut self.pointers, &mut *v);
142138
drop(v);
143139

144-
// release py objects
140+
// release PyObjects
145141
for ptr in vec.iter_mut() {
146142
ffi::Py_DECREF(ptr.as_ptr());
147143
}
148144
vec.set_len(0);
149145
}
150146

151147
pub unsafe fn drain(&mut self, owned: usize, borrowed: usize, pointers: bool) {
152-
let len = self.owned.len();
153-
if owned < len {
154-
for ptr in &mut self.owned[owned..len] {
155-
ffi::Py_DECREF(ptr.as_ptr());
156-
}
157-
self.owned.set_len(owned);
158-
}
159-
160-
let len = self.borrowed.len();
161-
if borrowed < len {
162-
self.borrowed.set_len(borrowed);
148+
// Release owned objects(call decref)
149+
while owned < self.owned.len() {
150+
let last = self.owned.pop_back().unwrap();
151+
ffi::Py_DECREF(last.as_ptr());
163152
}
153+
// Release borrowed objects(don't call decref)
154+
self.borrowed.truncate(borrowed);
164155

165156
if pointers {
166157
self.release_pointers();
@@ -232,23 +223,18 @@ pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T {
232223
}
233224

234225
pub unsafe fn register_pointer(obj: NonNull<ffi::PyObject>) {
235-
let pool: &'static mut ReleasePool = &mut *POOL;
236-
237-
let mut v = pool.p.lock();
238-
let pool: &'static mut Vec<_> = &mut *(*v);
239-
pool.push(obj);
226+
let pool = &mut *POOL;
227+
(**pool.p.lock()).push(obj);
240228
}
241229

242230
pub unsafe fn register_owned(_py: Python, obj: NonNull<ffi::PyObject>) -> &PyObjectRef {
243-
let pool: &'static mut ReleasePool = &mut *POOL;
244-
pool.owned.push(obj);
245-
&*(&pool.owned[pool.owned.len() - 1] as *const _ as *const PyObjectRef)
231+
let pool = &mut *POOL;
232+
&*(pool.owned.push_back(obj) as *const _ as *const PyObjectRef)
246233
}
247234

248235
pub unsafe fn register_borrowed(_py: Python, obj: NonNull<ffi::PyObject>) -> &PyObjectRef {
249-
let pool: &'static mut ReleasePool = &mut *POOL;
250-
pool.borrowed.push(obj);
251-
&*(&pool.borrowed[pool.borrowed.len() - 1] as *const _ as *const PyObjectRef)
236+
let pool = &mut *POOL;
237+
&*(pool.borrowed.push_back(obj) as *const _ as *const PyObjectRef)
252238
}
253239

254240
impl GILGuard {
@@ -278,6 +264,64 @@ impl GILGuard {
278264
}
279265
}
280266

267+
use self::array_list::ArrayList;
268+
269+
mod array_list {
270+
use std::collections::LinkedList;
271+
use std::mem;
272+
273+
const BLOCK_SIZE: usize = 256;
274+
275+
/// A container type for Release Pool
276+
/// See #271 for why this is crated
277+
pub(super) struct ArrayList<T> {
278+
inner: LinkedList<[T; BLOCK_SIZE]>,
279+
length: usize,
280+
}
281+
282+
impl<T: Clone> ArrayList<T> {
283+
pub fn new() -> Self {
284+
ArrayList {
285+
inner: LinkedList::new(),
286+
length: 0,
287+
}
288+
}
289+
pub fn push_back(&mut self, item: T) -> &T {
290+
let next_idx = self.next_idx();
291+
if next_idx == 0 {
292+
self.inner.push_back(unsafe { mem::uninitialized() });
293+
}
294+
self.inner.back_mut().unwrap()[next_idx] = item;
295+
self.length += 1;
296+
&self.inner.back().unwrap()[next_idx]
297+
}
298+
pub fn pop_back(&mut self) -> Option<T> {
299+
self.length -= 1;
300+
let current_idx = self.next_idx();
301+
if self.length >= BLOCK_SIZE && current_idx == 0 {
302+
let last_list = self.inner.pop_back()?;
303+
return Some(last_list[0].clone());
304+
}
305+
self.inner.back().map(|arr| arr[current_idx].clone())
306+
}
307+
pub fn len(&self) -> usize {
308+
self.length
309+
}
310+
pub fn truncate(&mut self, new_len: usize) {
311+
if self.length <= new_len {
312+
return;
313+
}
314+
while self.inner.len() > (new_len + BLOCK_SIZE - 1) / BLOCK_SIZE {
315+
self.inner.pop_back();
316+
}
317+
self.length = new_len;
318+
}
319+
fn next_idx(&self) -> usize {
320+
self.length % BLOCK_SIZE
321+
}
322+
}
323+
}
324+
281325
#[cfg(test)]
282326
mod test {
283327
use super::{GILPool, NonNull, ReleasePool, POOL};

tests/test_dict_iter.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extern crate pyo3;
2+
3+
use pyo3::{prelude::*, types::IntoPyDict};
4+
5+
#[test]
6+
fn iter_dict_nosegv() {
7+
let gil = Python::acquire_gil();
8+
let py = gil.python();
9+
const LEN: usize = 10_000_000;
10+
let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py);
11+
let mut sum = 0;
12+
for (k, _v) in dict.iter() {
13+
let i: u64 = k.extract().unwrap();
14+
sum += i;
15+
}
16+
assert_eq!(sum, 49999995000000);
17+
}

0 commit comments

Comments
 (0)