Skip to content

Commit 1a8ea61

Browse files
authored
[mypyc] Avoid boxing/unboxing when coercing between tuple types (#14899)
Instead, coerce each tuple item individually. This makes some coercions between tuple types much faster, primarily because there is less (or no) allocation and deallocation going on. This speeds up the raytrace benchmark by about 7% (when using native floats). Related to mypyc/mypyc#99.
1 parent 4a54894 commit 1a8ea61

File tree

4 files changed

+134
-11
lines changed

4 files changed

+134
-11
lines changed

Diff for: mypyc/irbuild/ll_builder.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
SetMem,
6969
Truncate,
7070
TupleGet,
71+
TupleSet,
7172
Unbox,
7273
Unreachable,
7374
Value,
@@ -354,11 +355,33 @@ def coerce(
354355
return Float(float(src.value))
355356
elif is_tagged(src_type) and is_float_rprimitive(target_type):
356357
return self.int_to_float(src, line)
357-
else:
358-
# To go from one unboxed type to another, we go through a boxed
359-
# in-between value, for simplicity.
360-
tmp = self.box(src)
361-
return self.unbox_or_cast(tmp, target_type, line)
358+
elif (
359+
isinstance(src_type, RTuple)
360+
and isinstance(target_type, RTuple)
361+
and len(src_type.types) == len(target_type.types)
362+
):
363+
# Coerce between two tuple types by coercing each item separately
364+
values = []
365+
for i in range(len(src_type.types)):
366+
v = None
367+
if isinstance(src, TupleSet):
368+
item = src.items[i]
369+
# We can't reuse register values, since they can be modified.
370+
if not isinstance(item, Register):
371+
v = item
372+
if v is None:
373+
v = TupleGet(src, i)
374+
self.add(v)
375+
values.append(v)
376+
return self.add(
377+
TupleSet(
378+
[self.coerce(v, t, line) for v, t in zip(values, target_type.types)], line
379+
)
380+
)
381+
# To go between any other unboxed types, we go through a boxed
382+
# in-between value, for simplicity.
383+
tmp = self.box(src)
384+
return self.unbox_or_cast(tmp, target_type, line)
362385
if (not src_type.is_unboxed and target_type.is_unboxed) or not is_subtype(
363386
src_type, target_type
364387
):

Diff for: mypyc/test-data/irbuild-float.test

+20
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,23 @@ def init(n: int) -> None:
475475
# narrowing assignments, generate errors here
476476
x: float = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float")
477477
y: float = 5 # E: Incompatible value representations in assignment (expression has type "int", variable has type "float")
478+
479+
[case testFloatCoerceTupleFromIntValues]
480+
from __future__ import annotations
481+
482+
def f(x: int) -> None:
483+
t: tuple[float, float, float] = (x, 2.5, -7)
484+
[out]
485+
def f(x):
486+
x :: int
487+
r0 :: tuple[int, float, int]
488+
r1 :: int
489+
r2 :: float
490+
r3, t :: tuple[float, float, float]
491+
L0:
492+
r0 = (x, 2.5, -14)
493+
r1 = r0[0]
494+
r2 = CPyFloat_FromTagged(r1)
495+
r3 = (r2, 2.5, -7.0)
496+
t = r3
497+
return 1

Diff for: mypyc/test-data/irbuild-i64.test

+78-6
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,6 @@ def f(x: i64, y: i64) -> Tuple[i64, i64]:
650650
return x, y
651651

652652
def g() -> Tuple[i64, i64]:
653-
# TODO: Avoid boxing and unboxing
654653
return 1, 2
655654

656655
def h() -> i64:
@@ -666,13 +665,11 @@ L0:
666665
return r0
667666
def g():
668667
r0 :: tuple[int, int]
669-
r1 :: object
670-
r2 :: tuple[int64, int64]
668+
r1 :: tuple[int64, int64]
671669
L0:
672670
r0 = (2, 4)
673-
r1 = box(tuple[int, int], r0)
674-
r2 = unbox(tuple[int64, int64], r1)
675-
return r2
671+
r1 = (1, 2)
672+
return r1
676673
def h():
677674
r0 :: tuple[int64, int64]
678675
r1, x, r2, y :: int64
@@ -2081,3 +2078,78 @@ L2:
20812078
r6 = r5.a
20822079
keep_alive x
20832080
return r6
2081+
2082+
[case testI64ConvertBetweenTuples_64bit]
2083+
from __future__ import annotations
2084+
from mypy_extensions import i64
2085+
2086+
def f(t: tuple[int, i64, int]) -> None:
2087+
tt: tuple[int, i64, i64] = t
2088+
2089+
def g(n: int) -> None:
2090+
t: tuple[i64, i64] = (1, n)
2091+
[out]
2092+
def f(t):
2093+
t :: tuple[int, int64, int]
2094+
r0 :: int
2095+
r1 :: int64
2096+
r2 :: int
2097+
r3 :: native_int
2098+
r4 :: bit
2099+
r5, r6 :: int64
2100+
r7 :: ptr
2101+
r8 :: c_ptr
2102+
r9 :: int64
2103+
r10, tt :: tuple[int, int64, int64]
2104+
L0:
2105+
r0 = t[0]
2106+
r1 = t[1]
2107+
r2 = t[2]
2108+
r3 = r2 & 1
2109+
r4 = r3 == 0
2110+
if r4 goto L1 else goto L2 :: bool
2111+
L1:
2112+
r5 = r2 >> 1
2113+
r6 = r5
2114+
goto L3
2115+
L2:
2116+
r7 = r2 ^ 1
2117+
r8 = r7
2118+
r9 = CPyLong_AsInt64(r8)
2119+
r6 = r9
2120+
keep_alive r2
2121+
L3:
2122+
r10 = (r0, r1, r6)
2123+
tt = r10
2124+
return 1
2125+
def g(n):
2126+
n :: int
2127+
r0 :: tuple[int, int]
2128+
r1 :: int
2129+
r2 :: native_int
2130+
r3 :: bit
2131+
r4, r5 :: int64
2132+
r6 :: ptr
2133+
r7 :: c_ptr
2134+
r8 :: int64
2135+
r9, t :: tuple[int64, int64]
2136+
L0:
2137+
r0 = (2, n)
2138+
r1 = r0[1]
2139+
r2 = r1 & 1
2140+
r3 = r2 == 0
2141+
if r3 goto L1 else goto L2 :: bool
2142+
L1:
2143+
r4 = r1 >> 1
2144+
r5 = r4
2145+
goto L3
2146+
L2:
2147+
r6 = r1 ^ 1
2148+
r7 = r6
2149+
r8 = CPyLong_AsInt64(r7)
2150+
r5 = r8
2151+
keep_alive r1
2152+
L3:
2153+
r9 = (1, r5)
2154+
t = r9
2155+
return 1

Diff for: mypyc/test-data/run-floats.test

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Test cases for floats (compile and run)
22

33
[case testFloatOps]
4+
from __future__ import annotations
45
from typing import Any, cast
56
from typing_extensions import Final
67
from testutil import assertRaises, float_vals, FLOAT_MAGIC
@@ -329,6 +330,13 @@ def test_undefined_local_var() -> None:
329330
with assertRaises(UnboundLocalError, 'local variable "y2" referenced before assignment'):
330331
print(y2)
331332

333+
def test_tuples() -> None:
334+
t1: tuple[float, float] = (1.5, 2.5)
335+
assert t1 == tuple([1.5, 2.5])
336+
n = int() + 5
337+
t2: tuple[float, float, float, float] = (n, 1.5, -7, -113)
338+
assert t2 == tuple([5.0, 1.5, -7.0, -113.0])
339+
332340
[case testFloatGlueMethodsAndInheritance]
333341
from typing import Any
334342
from typing_extensions import Final

0 commit comments

Comments
 (0)