Skip to content

Commit 6c1776f

Browse files
committed
py/objslice: Avoid heap-allocating slices for built-ins.
Assume that a slice object (created by the `BUILD_SLICE` op-code or the native emitter via `mp_obj_new_slice`) will be used to slice a built-in type. This assumes that all built-in type->subscr functions will: 1. Not cause another slice to be allocated in the meantime. 2. Not save a reference to the slice. This prevents a heap allocation, which for some cases (e.g. memoryview assignment) prevents allocation altogether. Only when this assumption is incorrect (i.e. slicing a user-defined type via the `__{get,set,del}item__` methods), then heap-allocate the slice object. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent fb7d211 commit 6c1776f

File tree

5 files changed

+73
-3
lines changed

5 files changed

+73
-3
lines changed

py/mpstate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ typedef struct _mp_state_thread_t {
256256
// Locking of the GC is done per thread.
257257
uint16_t gc_lock_depth;
258258

259+
mp_obj_slice_t slice;
260+
259261
////////////////////////////////////////////////////////////
260262
// START ROOT POINTER SECTION
261263
// Everything that needs GC scanning must start here, and

py/objslice.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,18 @@ MP_DEFINE_CONST_OBJ_TYPE(
109109
);
110110

111111
mp_obj_t mp_obj_new_slice(mp_obj_t ostart, mp_obj_t ostop, mp_obj_t ostep) {
112-
mp_obj_slice_t *o = mp_obj_malloc(mp_obj_slice_t, &mp_type_slice);
112+
// Use the thread-local slice object, assuming that the likely case is
113+
// that this will be passed to a built-in type->subscr which will:
114+
// 1. Not cause another slice to be allocated in the meantime.
115+
// 2. Not save a reference to the slice.
116+
// The only exception to this is instance_subscr which will copy the slice
117+
// object into the heap before calling the user-defined __
118+
// {get,set,del}item__.
119+
// This prevents allocation of slice objects for all built-in types, as
120+
// well as making some specific cases (e.g. memoryview or bytearray slice
121+
// assignment) completely allocation-free.
122+
mp_obj_slice_t *o = &MP_STATE_THREAD(slice);
123+
o->base.type = &mp_type_slice;
113124
o->start = ostart;
114125
o->stop = ostop;
115126
o->step = ostep;

py/objtype.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,20 @@ STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value
837837
return mp_obj_subscr(self->subobj[0], index, value);
838838
} else if (member[0] != MP_OBJ_NULL) {
839839
size_t n_args = value == MP_OBJ_NULL || value == MP_OBJ_SENTINEL ? 1 : 2;
840+
841+
#if MICROPY_PY_BUILTINS_SLICE
842+
if (mp_obj_is_type(index, &mp_type_slice)) {
843+
// By default the slice object will be thread-local under the
844+
// assumption that built-in subscr methods will be safe. However
845+
// in this case we're calling a user-defined {get,set,del}item
846+
// handler and cannot make any assumptions, so copy it to the heap.
847+
// See mp_obj_new_slice for details.
848+
mp_obj_slice_t *o = m_new(mp_obj_slice_t, 1);
849+
memcpy(o, MP_OBJ_TO_PTR(index), sizeof(mp_obj_slice_t));
850+
member[2] = MP_OBJ_FROM_PTR(o);
851+
}
852+
#endif
853+
840854
mp_obj_t ret = mp_call_method_n_kw(n_args, 0, member);
841855
if (value == MP_OBJ_SENTINEL) {
842856
return ret;

tests/basics/builtin_slice.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
11
# test builtin slice
22

3+
# ensures that slices passed to user types are heap-allocated and can be
4+
# safely stored as well as not overriden by subsequent slices.
5+
36
# print slice
47
class A:
58
def __getitem__(self, idx):
6-
print(idx)
9+
print("get", idx)
10+
print("abc"[1:])
11+
print("get", idx)
12+
return idx
13+
14+
def __setitem__(self, idx, value):
15+
print("set", idx)
16+
print("abc"[1:])
17+
print("set", idx)
18+
self.saved_idx = idx
19+
return idx
20+
21+
def __delitem__(self, idx):
22+
print("del", idx)
23+
print("abc"[1:])
24+
print("del", idx)
725
return idx
8-
s = A()[1:2:3]
26+
27+
a = A()
28+
s = a[1:2:3]
29+
a[4:5:6] = s
30+
del a[7:8:9]
31+
32+
print(a.saved_idx)
33+
934

1035
# check type
1136
print(type(s) is slice)

tests/micropython/heapalloc_slice.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# slice operations that don't require allocation
2+
try:
3+
from micropython import heap_lock, heap_unlock
4+
except (ImportError, AttributeError):
5+
heap_lock = heap_unlock = lambda: 0
6+
7+
b = bytearray(range(10))
8+
9+
m = memoryview(b)
10+
11+
heap_lock()
12+
13+
b[3:5] = b"aa"
14+
m[5:7] = b"bb"
15+
16+
heap_unlock()
17+
18+
print(b)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy