Skip to content

Commit 0c9a2a6

Browse files
committed
Backport #281
1 parent 9f45efe commit 0c9a2a6

File tree

3 files changed

+119
-37
lines changed

3 files changed

+119
-37
lines changed

benches/bench_dict.rs

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

src/pythonrun.rs

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,18 @@ impl Drop for GILGuard {
107107

108108
/// Release pool
109109
struct ReleasePool {
110-
owned: Vec<*mut ffi::PyObject>,
111-
borrowed: Vec<*mut ffi::PyObject>,
112-
pointers: *mut Vec<*mut ffi::PyObject>,
110+
owned: ArrayList<NonNull<ffi::PyObject>>,
111+
borrowed: ArrayList<NonNull<ffi::PyObject>>,
112+
pointers: *mut Vec<NonNull<ffi::PyObject>>,
113113
obj: Vec<Box<any::Any>>,
114114
p: spin::Mutex<*mut Vec<*mut ffi::PyObject>>,
115115
}
116116

117117
impl ReleasePool {
118118
fn new() -> ReleasePool {
119119
ReleasePool {
120-
owned: Vec::with_capacity(256),
121-
borrowed: Vec::with_capacity(256),
120+
owned: ArrayList::new(),
121+
borrowed: ArrayList::new(),
122122
pointers: Box::into_raw(Box::new(Vec::with_capacity(256))),
123123
obj: Vec::with_capacity(8),
124124
p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(256)))),
@@ -127,39 +127,30 @@ impl ReleasePool {
127127

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

138135
// switch vectors
139-
*v = self.pointers;
140-
self.pointers = ptr;
136+
std::mem::swap(&mut self.pointers, &mut *v);
141137
drop(v);
142138

143-
// release py objects
139+
// release PyObjects
144140
for ptr in vec.iter_mut() {
145141
ffi::Py_DECREF(*ptr);
146142
}
147143
vec.set_len(0);
148144
}
149145

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

164155
if pointers {
165156
self.release_pointers();
@@ -230,24 +221,19 @@ pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T {
230221
.unwrap()
231222
}
232223

233-
pub unsafe fn register_pointer(obj: NonNullPyObject) {
234-
let pool: &'static mut ReleasePool = &mut *POOL;
235-
236-
let mut v = pool.p.lock();
237-
let pool: &'static mut Vec<*mut ffi::PyObject> = &mut *(*v);
238-
pool.push(obj.as_ptr());
224+
pub unsafe fn register_pointer(obj: NonNull<ffi::PyObject>) {
225+
let pool = &mut *POOL;
226+
(**pool.p.lock()).push(obj);
239227
}
240228

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

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

253239
impl GILGuard {
@@ -277,6 +263,64 @@ impl GILGuard {
277263
}
278264
}
279265

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