Skip to content

Commit ec15a0b

Browse files
committed
py/vm.c: Avoid heap-allocating slices for built-ins.
Fast path optimisation for when a BUILD_SLICE is immediately followed by a LOAD/STORE_SUBSCR for a native type to avoid needing to allocate the slice on the heap. In some cases (e.g. `a[1:3] = x`) this can result in no allocations at all. We can't do this for instance types because the get/set/delattr implementation may keep a reference to the slice. Adds more tests to the basic slice tests to ensure that a stack-allocated slice never makes it to Python, and also a heapalloc test that verifies (when using bytecode) that assigning to a slice is no-alloc. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent d81cf0b commit ec15a0b

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

py/vm.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,26 @@ unwind_jump:;
850850
step = POP();
851851
}
852852
mp_obj_t stop = POP();
853-
mp_obj_t start = TOP();
854-
SET_TOP(mp_obj_new_slice(start, stop, step));
853+
mp_obj_t start = POP();
854+
if ((*ip == MP_BC_LOAD_SUBSCR || *ip == MP_BC_STORE_SUBSCR) && mp_obj_is_native_type(mp_obj_get_type(TOP()))) {
855+
// Fast path optimisation for when the BUILD_SLICE is
856+
// immediately followed by a LOAD/STORE_SUBSCR for a
857+
// native type to avoid needing to allocate the slice
858+
// on the heap. In some cases (e.g. a[1:3] = x) this
859+
// can result in no allocations at all. We can't do
860+
// this for instance types because the get/set/delattr
861+
// implementation may keep a reference to the slice.
862+
byte op = *ip++;
863+
mp_obj_slice_t slice = { .base = { .type = &mp_type_slice }, .start = start, .stop = stop, .step = step };
864+
if (op == MP_BC_LOAD_SUBSCR) {
865+
SET_TOP(mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), MP_OBJ_SENTINEL));
866+
} else { // MP_BC_STORE_SUBSCR
867+
mp_obj_subscr(TOP(), MP_OBJ_FROM_PTR(&slice), sp[-1]);
868+
sp -= 2;
869+
}
870+
} else {
871+
PUSH(mp_obj_new_slice(start, stop, step));
872+
}
855873
DISPATCH();
856874
}
857875
#endif

tests/basics/builtin_slice.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,44 @@
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+
34+
# nested slicing
35+
print(A()[1:A()[A()[2:3:4]:5]])
36+
37+
# tuple slicing
38+
a[1:2,4:5,7:8]
39+
a[1,4:5,7:8,2]
40+
a[1:2, a[3:4], 5:6]
941

1042
# check type
1143
print(type(s) is slice)
44+

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)

tests/run-tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,9 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
639639
skip_tests.add(
640640
"micropython/emg_exc.py"
641641
) # because native doesn't have proper traceback info
642+
skip_tests.add(
643+
"micropython/heapalloc_slice.py"
644+
) # because native doesn't do the stack-allocated slice optimisation
642645
skip_tests.add(
643646
"micropython/heapalloc_traceback.py"
644647
) # because native doesn't have proper traceback info

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