Skip to content

Commit 2d08116

Browse files
JukkaLp-sawicki
andauthored
[mypyc] Optimize list->vec and tuple->vec conversions (#21375)
Now we specialize these conversions for each vec variant (except for nested, where it doesn't seem as useful). Previously they'd use append for each item, which tends to be slow. They also used generic iterators in some use cases, which is inefficient, especially for a small number of items where the iterator construction and freeing is significant. --------- Co-authored-by: Piotr Sawicki <sawickipiotr@outlook.com>
1 parent ddacf23 commit 2d08116

12 files changed

Lines changed: 271 additions & 13 deletions

mypyc/irbuild/expression.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
is_list_rprimitive,
9494
is_none_rprimitive,
9595
is_object_rprimitive,
96+
is_tuple_rprimitive,
9697
object_rprimitive,
9798
set_rprimitive,
9899
vec_api_by_item_type,
@@ -127,6 +128,7 @@
127128
vec_create,
128129
vec_create_from_values,
129130
vec_create_initialized,
131+
vec_item_type,
130132
vec_slice,
131133
)
132134
from mypyc.primitives.bytes_ops import bytes_slice_op
@@ -630,26 +632,40 @@ def vec_from_iterable(
630632
item_type = vec_type.item_type
631633
api_name = vec_api_by_item_type.get(item_type)
632634
iterable_rtype = builder.node_type(iterable)
633-
if api_name is not None and (
635+
use_c_from_iterable = (
634636
is_object_rprimitive(iterable_rtype)
637+
or is_list_rprimitive(iterable_rtype)
638+
or is_tuple_rprimitive(iterable_rtype)
639+
)
640+
if api_name is not None and (
641+
use_c_from_iterable
635642
or is_bytes_rprimitive(iterable_rtype)
636643
or is_bytearray_rprimitive(iterable_rtype)
637644
):
638645
# For generic iterables (typed as object) and bytes/bytearray
639646
# (which support the buffer protocol for fast memcpy), call the
640647
# C-level from_iterable. For concrete types like range, list,
641648
# vec, etc., the for-loop desugaring below produces better IR.
649+
name = f"{api_name}.from_iterable"
650+
extra_args: list[Value] = []
651+
elif api_name is None and vec_type.depth() == 0 and use_c_from_iterable:
652+
name = "VecTApi.from_iterable"
653+
extra_args = [vec_item_type(builder.builder, item_type, line)]
654+
else:
655+
name = None
656+
if name is not None:
642657
iterable_val = builder.accept(iterable)
643658
cap = (
644659
as_platform_int(builder.builder, capacity, line)
645660
if capacity is not None
646661
else Integer(0, int64_rprimitive)
647662
)
663+
args = extra_args + [iterable_val, cap]
648664
call = CallC(
649-
f"{api_name}.from_iterable",
650-
[iterable_val, cap],
665+
name,
666+
args,
651667
vec_type,
652-
steals=[False, False],
668+
steals=[False] * len(args),
653669
is_borrowed=False,
654670
error_kind=ERR_MAGIC,
655671
line=line,

mypyc/lib-rt/vecs/librt_vecs.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ static PyObject *vec_generic_alias_call(PyObject *self, PyObject *args, PyObject
114114
return NULL;
115115
return VecT_Box(vec, p->item_type);
116116
} else {
117-
return VecT_FromIterable(p->item_type, init, cap);
117+
VecT vec = VecT_FromIterable(p->item_type, init, cap);
118+
if (VEC_IS_ERROR(vec))
119+
return NULL;
120+
return VecT_Box(vec, p->item_type);
118121
}
119122
} else {
120123
if (init == NULL) {

mypyc/lib-rt/vecs/librt_vecs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ typedef struct _VecTAPI {
400400
VecT (*remove)(VecT, PyObject *);
401401
// TODO: Py_ssize_t
402402
VecT (*slice)(VecT, int64_t, int64_t);
403+
VecT (*from_iterable)(size_t, PyObject *, int64_t);
403404
VecT (*extend)(VecT, PyObject *, size_t);
404405
VecT (*extend_vec)(VecT, VecT, size_t);
405406
} VecTAPI;
@@ -716,7 +717,7 @@ static inline int VecT_ItemCheck(VecT v, PyObject *item, size_t item_type) {
716717
}
717718

718719
VecT VecT_New(Py_ssize_t size, Py_ssize_t cap, size_t item_type);
719-
PyObject *VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap);
720+
VecT VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap);
720721
PyObject *VecT_Box(VecT vec, size_t item_type);
721722
VecT VecT_Append(VecT vec, PyObject *x, size_t item_type);
722723
VecT VecT_Extend(VecT vec, PyObject *iterable, size_t item_type);

mypyc/lib-rt/vecs/vec_t.c

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -685,10 +685,41 @@ PyTypeObject VecTType = {
685685
// TODO: free
686686
};
687687

688-
PyObject *VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap) {
688+
static inline VecT vec_from_sequence(
689+
size_t item_type, PyObject *seq, int64_t cap, const int is_list) {
690+
Py_ssize_t n = is_list ? PyList_GET_SIZE(seq) : PyTuple_GET_SIZE(seq);
691+
Py_ssize_t alloc_size = n > cap ? n : cap;
692+
VecT v = vec_alloc(alloc_size, item_type);
693+
if (VEC_IS_ERROR(v))
694+
return vec_error();
695+
for (Py_ssize_t i = 0; i < n; i++) {
696+
PyObject *item = is_list ? PyList_GET_ITEM(seq, i) : PyTuple_GET_ITEM(seq, i);
697+
if (!VecT_ItemCheck(v, item, item_type)) {
698+
for (Py_ssize_t j = i; j < alloc_size; j++)
699+
v.buf->items[j] = NULL;
700+
VEC_DECREF(v);
701+
return vec_error();
702+
}
703+
Py_INCREF(item);
704+
v.buf->items[i] = item;
705+
}
706+
for (Py_ssize_t j = n; j < alloc_size; j++)
707+
v.buf->items[j] = NULL;
708+
vec_track_buffer(&v);
709+
v.len = n;
710+
return v;
711+
}
712+
713+
VecT VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap) {
714+
if (PyList_CheckExact(iterable)) {
715+
return vec_from_sequence(item_type, iterable, cap, 1);
716+
} else if (PyTuple_CheckExact(iterable)) {
717+
return vec_from_sequence(item_type, iterable, cap, 0);
718+
}
719+
689720
VecT v = vec_alloc(cap, item_type);
690721
if (VEC_IS_ERROR(v))
691-
return NULL;
722+
return vec_error();
692723
if (cap > 0) {
693724
for (int64_t i = 0; i < cap; i++)
694725
v.buf->items[i] = NULL;
@@ -699,30 +730,30 @@ PyObject *VecT_FromIterable(size_t item_type, PyObject *iterable, int64_t cap) {
699730
PyObject *iter = PyObject_GetIter(iterable);
700731
if (iter == NULL) {
701732
VEC_DECREF(v);
702-
return NULL;
733+
return vec_error();
703734
}
704735
PyObject *item;
705736
while ((item = PyIter_Next(iter)) != NULL) {
706737
if (!VecT_ItemCheck(v, item, item_type)) {
707738
Py_DECREF(iter);
708739
VEC_DECREF(v);
709740
Py_DECREF(item);
710-
return NULL;
741+
return vec_error();
711742
}
712743
v = VecT_Append(v, item, item_type);
713744
Py_DECREF(item);
714745
if (VEC_IS_ERROR(v)) {
715746
Py_DECREF(iter);
716747
VEC_DECREF(v);
717-
return NULL;
748+
return vec_error();
718749
}
719750
}
720751
Py_DECREF(iter);
721752
if (PyErr_Occurred()) {
722753
VEC_DECREF(v);
723-
return NULL;
754+
return vec_error();
724755
}
725-
return VecT_Box(v, item_type);
756+
return v;
726757
}
727758

728759
VecTAPI Vec_TAPI = {
@@ -736,6 +767,7 @@ VecTAPI Vec_TAPI = {
736767
VecT_Pop,
737768
VecT_Remove,
738769
VecT_Slice,
770+
VecT_FromIterable,
739771
VecT_Extend,
740772
VecT_ExtendVec,
741773
};

mypyc/lib-rt/vecs/vec_template.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ inline static int vec_get_buffer(PyObject *obj, Py_buffer *view) {
135135
}
136136
#endif
137137

138+
static inline VEC vec_from_sequence(PyObject *seq, int64_t cap, const int is_list) {
139+
Py_ssize_t n = is_list ? PyList_GET_SIZE(seq) : PyTuple_GET_SIZE(seq);
140+
Py_ssize_t alloc_size = n > cap ? n : cap;
141+
VEC v = vec_alloc(alloc_size);
142+
if (VEC_IS_ERROR(v))
143+
return vec_error();
144+
for (Py_ssize_t i = 0; i < n; i++) {
145+
PyObject *item = is_list ? PyList_GET_ITEM(seq, i) : PyTuple_GET_ITEM(seq, i);
146+
ITEM_C_TYPE x = UNBOX_ITEM(item);
147+
if (IS_UNBOX_ERROR(x)) {
148+
VEC_DECREF(v);
149+
return vec_error();
150+
}
151+
v.buf->items[i] = x;
152+
}
153+
v.len = n;
154+
return v;
155+
}
156+
138157
VEC FUNC(FromIterable)(PyObject *iterable, int64_t cap) {
139158
if (cap < 0) {
140159
PyErr_SetString(PyExc_ValueError, "capacity must not be negative");
@@ -175,6 +194,12 @@ VEC FUNC(FromIterable)(PyObject *iterable, int64_t cap) {
175194
}
176195
#endif
177196

197+
if (PyList_CheckExact(iterable)) {
198+
return vec_from_sequence(iterable, cap, 1);
199+
} else if (PyTuple_CheckExact(iterable)) {
200+
return vec_from_sequence(iterable, cap, 0);
201+
}
202+
178203
VEC v = vec_alloc(cap);
179204
if (VEC_IS_ERROR(v))
180205
return vec_error();

mypyc/test-data/irbuild-vec-i64.test

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,3 +943,31 @@ def f(x):
943943
L0:
944944
r0 = VecI64Api.from_iterable(x, 0)
945945
return r0
946+
947+
[case testVecI64CreateFromListObject]
948+
from librt.vecs import vec
949+
from mypy_extensions import i64
950+
951+
def from_list(a: list[i64]) -> vec[i64]:
952+
return vec[i64](a)
953+
[out]
954+
def from_list(a):
955+
a :: list
956+
r0 :: vec[i64]
957+
L0:
958+
r0 = VecI64Api.from_iterable(a, 0)
959+
return r0
960+
961+
[case testVecI64CreateFromTupleObject]
962+
from librt.vecs import vec
963+
from mypy_extensions import i64
964+
965+
def from_tuple(a: tuple[i64, ...]) -> vec[i64]:
966+
return vec[i64](a)
967+
[out]
968+
def from_tuple(a):
969+
a :: tuple
970+
r0 :: vec[i64]
971+
L0:
972+
r0 = VecI64Api.from_iterable(a, 0)
973+
return r0

mypyc/test-data/irbuild-vec-t.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,54 @@ L0:
453453
r8 = r7 + 8
454454
keep_alive r4
455455
return r4
456+
457+
[case testVecTConstructFromIterable]
458+
from typing import Iterable
459+
from librt.vecs import vec
460+
461+
def f(x: Iterable[str]) -> vec[str]:
462+
return vec[str](x)
463+
[out]
464+
def f(x):
465+
x, r0 :: object
466+
r1 :: ptr
467+
r2 :: vec[str]
468+
L0:
469+
r0 = load_address PyUnicode_Type
470+
r1 = r0
471+
r2 = VecTApi.from_iterable(r1, x, 0)
472+
return r2
473+
474+
[case testVecTCreateFromListObject]
475+
from librt.vecs import vec
476+
477+
def from_list(a: list[str]) -> vec[str]:
478+
return vec[str](a)
479+
[out]
480+
def from_list(a):
481+
a :: list
482+
r0 :: object
483+
r1 :: ptr
484+
r2 :: vec[str]
485+
L0:
486+
r0 = load_address PyUnicode_Type
487+
r1 = r0
488+
r2 = VecTApi.from_iterable(r1, a, 0)
489+
return r2
490+
491+
[case testVecTCreateFromTupleObject]
492+
from librt.vecs import vec
493+
494+
def from_tuple(a: tuple[str, ...]) -> vec[str]:
495+
return vec[str](a)
496+
[out]
497+
def from_tuple(a):
498+
a :: tuple
499+
r0 :: object
500+
r1 :: ptr
501+
r2 :: vec[str]
502+
L0:
503+
r0 = load_address PyUnicode_Type
504+
r1 = r0
505+
r2 = VecTApi.from_iterable(r1, a, 0)
506+
return r2

mypyc/test-data/run-vecs-i64-interp.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,40 @@ def test_construct_from_initializer() -> None:
4444
with assertRaises(TypeError):
4545
vec[i64]([None])
4646

47+
def test_construct_from_tuple() -> None:
48+
v = vec[i64](())
49+
assert len(v) == 0
50+
v = vec[i64]((1, 2, 3))
51+
assert len(v) == 3
52+
assert v[0] == 1
53+
assert v[1] == 2
54+
assert v[2] == 3
55+
v = vec[i64]((2**63 - 1, -2**63))
56+
assert len(v) == 2
57+
assert v[0] == 2**63 - 1
58+
assert v[1] == -2**63
59+
with assertRaises(TypeError):
60+
vec[i64](('x',))
61+
with assertRaises(TypeError):
62+
vec[i64]((None,))
63+
with assertRaises(TypeError):
64+
vec[i64]((1, 'x', 2))
65+
66+
def test_construct_from_tuple_with_capacity() -> None:
67+
v = vec[i64]((1, 2), capacity=5)
68+
assert len(v) == 2
69+
assert v[0] == 1
70+
assert v[1] == 2
71+
old = v
72+
v = append(v, 3)
73+
v = append(v, 4)
74+
v = append(v, 5)
75+
old[0] = 99
76+
assert v[0] == 99
77+
v = vec[i64]((1, 2, 3), capacity=1)
78+
assert len(v) == 3
79+
assert v[2] == 3
80+
4781
def test_append() -> None:
4882
v = vec[i64]()
4983
v = append(v, 1)

mypyc/test-data/run-vecs-misc-interp.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ def test_basic_int_operations() -> None:
9999
with assertRaises(TypeError):
100100
append(v, "0")
101101

102+
def test_construct_bool_from_list() -> None:
103+
v = vec[bool]([True, False, True])
104+
assert len(v) == 3
105+
assert v[0] is True
106+
assert v[1] is False
107+
assert v[2] is True
108+
102109
def test_equality() -> None:
103110
for t1 in ITEM_TYPES:
104111
for t2 in ITEM_TYPES:

mypyc/test-data/run-vecs-nested-interp.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ def test_construct_from_initializer_nested() -> None:
6363
with assertRaises(TypeError):
6464
vec[str]([vec[bytes]()])
6565

66+
def test_construct_from_tuple_nested() -> None:
67+
v = vec[vec[str]](())
68+
assert len(v) == 0
69+
v = vec[vec[str]]((vec[str](['a']), vec[str](['b', 'c'])))
70+
assert len(v) == 2
71+
assert v[0][0] == 'a'
72+
assert v[1][0] == 'b'
73+
assert v[1][1] == 'c'
74+
with assertRaises(TypeError):
75+
vec[vec[str]]((vec[str](), 'x'))
76+
6677
def test_isinstance() -> None:
6778
assert isinstance(vec[vec[str]](), vec)
6879
assert isinstance(vec[vec[Optional[str]]](), vec)

0 commit comments

Comments
 (0)