Skip to content

Commit b55ac53

Browse files
committed
py/modmicropython: Add micropython.memmove() and micropython.memset().
This was based on a discussion about providing a more optimal way to copy data between buffers, however based on the benchmarking so far it seems like it might not be worth the overhead. Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 00930b2 commit b55ac53

12 files changed

+243
-0
lines changed

py/modmicropython.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
*/
2626

2727
#include <stdio.h>
28+
#include <string.h>
2829

2930
#include "py/builtin.h"
31+
#include "py/obj.h"
3032
#include "py/stackctrl.h"
3133
#include "py/runtime.h"
3234
#include "py/gc.h"
@@ -166,6 +168,96 @@ STATIC mp_obj_t mp_micropython_schedule(mp_obj_t function, mp_obj_t arg) {
166168
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_micropython_schedule_obj, mp_micropython_schedule);
167169
#endif
168170

171+
#if MICROPY_ENABLE_MEM_FUNCTIONS
172+
173+
STATIC mp_obj_t mp_micropython_memmove(size_t n_args, const mp_obj_t *args) {
174+
enum { ARG_dest, ARG_dest_idx, ARG_src, ARG_src_idx, ARG_len }; // len is optional
175+
mp_buffer_info_t src, dest;
176+
mp_int_t src_idx, dest_idx, len, max_len;
177+
178+
mp_get_buffer_raise(args[ARG_dest], &dest, MP_BUFFER_WRITE);
179+
mp_get_buffer_raise(args[ARG_src], &src, MP_BUFFER_READ);
180+
dest_idx = mp_obj_get_int(args[ARG_dest_idx]);
181+
src_idx = mp_obj_get_int(args[ARG_src_idx]);
182+
183+
// similar to slice syntax, negative indexes are relative to the end
184+
if (dest_idx < 0) {
185+
dest_idx += dest.len;
186+
}
187+
if (src_idx < 0) {
188+
src_idx += src.len;
189+
}
190+
191+
if (dest_idx < 0 || src_idx < 0 || (size_t)dest_idx >= dest.len || (size_t)src_idx >= src.len) {
192+
// TODO: Maybe it's better to emulate slice syntax here and have ranges
193+
// past the end silently truncate to the end?
194+
mp_raise_type(&mp_type_IndexError); // TODO: ValueError? IndexError is what equivalent Python code would raise
195+
}
196+
197+
// The most bytes we can move
198+
max_len = MIN(dest.len - dest_idx, src.len - src_idx);
199+
200+
if (n_args > ARG_len) {
201+
len = mp_obj_get_int(args[ARG_len]);
202+
if (len < 0 || len > max_len) {
203+
// TODO: maybe better to truncate here as well?
204+
mp_raise_type(&mp_type_IndexError); // TODO: ValueError? see above
205+
}
206+
} else {
207+
len = max_len;
208+
}
209+
210+
memmove((char *)dest.buf + dest_idx, (char *)src.buf + src_idx, len);
211+
212+
return mp_obj_new_int(len);
213+
}
214+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_memmove_obj, 4, 5, mp_micropython_memmove);
215+
216+
STATIC mp_obj_t mp_micropython_memset(size_t n_args, const mp_obj_t *args) {
217+
enum { ARG_dest, ARG_dest_idx, ARG_c, ARG_len };
218+
mp_buffer_info_t dest;
219+
mp_int_t dest_idx = 0;
220+
uint8_t c = 0;
221+
mp_int_t len;
222+
223+
mp_get_buffer_raise(args[ARG_dest], &dest, MP_BUFFER_WRITE);
224+
if (n_args > ARG_dest_idx) {
225+
dest_idx = mp_obj_get_int(args[ARG_dest_idx]);
226+
227+
if (dest_idx < 0) {
228+
// Like slice syntax, negative value indexes from the end
229+
dest_idx += dest.len;
230+
}
231+
232+
if (dest_idx < 0 || (size_t)dest_idx >= dest.len) {
233+
mp_raise_type(&mp_type_IndexError); // TODO: ValueError? see above
234+
}
235+
}
236+
237+
if (n_args > ARG_c) {
238+
// To save code size, don't range-check if c is a single byte integer,
239+
// out of range values are truncated and then used.
240+
c = mp_obj_get_int(args[ARG_c]);
241+
}
242+
243+
if (n_args > ARG_len) {
244+
len = mp_obj_get_int(args[ARG_len]);
245+
if (len < 0 || (size_t)len > dest.len - dest_idx) {
246+
mp_raise_type(&mp_type_IndexError); // TODO: ValueError? see above
247+
}
248+
} else {
249+
len = dest.len - dest_idx;
250+
}
251+
252+
memset((char *)dest.buf + dest_idx, c, len);
253+
254+
return mp_obj_new_int(len);
255+
}
256+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_memset_obj, 1, 4, mp_micropython_memset);
257+
258+
#endif
259+
260+
169261
STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
170262
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_micropython) },
171263
{ MP_ROM_QSTR(MP_QSTR_const), MP_ROM_PTR(&mp_identity_obj) },
@@ -203,6 +295,10 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
203295
#if MICROPY_ENABLE_SCHEDULER
204296
{ MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) },
205297
#endif
298+
#if MICROPY_ENABLE_MEM_FUNCTIONS
299+
{ MP_ROM_QSTR(MP_QSTR_memmove), MP_ROM_PTR(&mp_micropython_memmove_obj) },
300+
{ MP_ROM_QSTR(MP_QSTR_memset), MP_ROM_PTR(&mp_micropython_memset_obj) },
301+
#endif
206302
};
207303

208304
STATIC MP_DEFINE_CONST_DICT(mp_module_micropython_globals, mp_module_micropython_globals_table);

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,11 @@ typedef double mp_float_t;
12561256
#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING)
12571257
#endif
12581258

1259+
// Whether to provide "micropython.memmove" and "micropython.memset" functions
1260+
#ifndef MICROPY_ENABLE_MEM_FUNCTIONS
1261+
#define MICROPY_ENABLE_MEM_FUNCTIONS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES)
1262+
#endif
1263+
12591264
// Whether to provide "array" module. Note that large chunk of the
12601265
// underlying code is shared with "bytearray" builtin type, so to
12611266
// get real savings, it should be disabled too.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
c = b"U"[0]
7+
i = 0
8+
while i < 20000000:
9+
for n in range(len(buf)):
10+
buf[n] = c
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
c = b"U"[0]
7+
i = 0
8+
while i < 20000000:
9+
n = 0 # compared to -1-naive.py, eliminate the for loop
10+
while n < 16:
11+
buf[n] = c
12+
n += 1
13+
i += 1
14+
15+
16+
bench.run(test)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
i = 0
7+
while i < 20000000:
8+
buf[:] = b"UUUUUUUUUUUUUUUU"
9+
i += 1
10+
11+
12+
bench.run(test)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import bench
2+
3+
from micropython import memset
4+
5+
6+
def test(num):
7+
buf = bytearray(16)
8+
c = b"U"[0]
9+
i = 0
10+
while i < 20000000:
11+
memset(buf, 0, c)
12+
i += 1
13+
14+
15+
bench.run(test)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
a = b"\x00\x01\x02\x03\x04\x05\x06\x07"
7+
i = 0
8+
while i < 20000000:
9+
# slice only the starting index of lvalue
10+
buf[8:] = a
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
a = b"\x00\x01\x02\x03\x04\x05\x06\x07"
7+
i = 0
8+
while i < 20000000:
9+
# slice the starting index and length of lvalue
10+
buf[8:16] = a
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = bytearray(16)
6+
a = b"\x00\x01\x02\x03\x04\x05\x06\x07"
7+
i = 0
8+
while i < 20000000:
9+
# slice the starting index of lvalue and rvalue
10+
buf[8:] = a[0:]
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = memoryview(bytearray(16))
6+
a = b"\x00\x01\x02\x03\x04\x05\x06\x07"
7+
i = 0
8+
while i < 20000000:
9+
# slice the memoryview lvalue
10+
buf[8:] = a
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import bench
2+
3+
4+
def test(num):
5+
buf = memoryview(bytearray(16))
6+
a = memoryview(b"\x00\x01\x02\x03\x04\x05\x06\x07")
7+
i = 0
8+
while i < 20000000:
9+
# slice both the lvalue & rvalue memoryviews
10+
buf[8:] = a[0:]
11+
i += 1
12+
13+
14+
bench.run(test)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import bench
2+
3+
from micropython import memmove
4+
5+
6+
def test(num):
7+
buf = bytearray(16)
8+
a = b"\x00\x01\x02\x03\x04\x05\x06\x07"
9+
i = 0
10+
while i < 20000000:
11+
memmove(buf, 8, a, 0) # implicit 'len'
12+
i += 1
13+
14+
15+
bench.run(test)

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