From 1fd6093e5e9b38d59bb43ab0e4209807997039d2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 12 Mar 2023 11:14:04 +0000 Subject: [PATCH 1/2] [mypyc] Avoid boxing/unboxing when coercing between tuple types 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. --- mypyc/irbuild/ll_builder.py | 33 ++++++++++-- mypyc/test-data/irbuild-float.test | 20 +++++++ mypyc/test-data/irbuild-i64.test | 84 +++++++++++++++++++++++++++--- mypyc/test-data/run-floats.test | 8 +++ 4 files changed, 134 insertions(+), 11 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index d41b532f9228..6a4599a324d4 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -68,6 +68,7 @@ SetMem, Truncate, TupleGet, + TupleSet, Unbox, Unreachable, Value, @@ -354,11 +355,33 @@ def coerce( return Float(float(src.value)) elif is_tagged(src_type) and is_float_rprimitive(target_type): return self.int_to_float(src, line) - else: - # To go from one unboxed type to another, we go through a boxed - # in-between value, for simplicity. - tmp = self.box(src) - return self.unbox_or_cast(tmp, target_type, line) + elif ( + isinstance(src_type, RTuple) + and isinstance(target_type, RTuple) + and len(src_type.types) == len(target_type.types) + ): + # Coerce between two tuple types by coercing each item separately + values = [] + for i in range(len(src_type.types)): + v = None + if isinstance(src, TupleSet): + item = src.items[i] + # We can't reuse register values, since they can be modified. + if not isinstance(item, Register): + v = item + if v is None: + v = TupleGet(src, i) + self.add(v) + values.append(v) + return self.add( + TupleSet( + [self.coerce(v, t, line) for v, t in zip(values, target_type.types)], line + ) + ) + # To go between any other unboxed types, we go through a boxed + # in-between value, for simplicity. + tmp = self.box(src) + return self.unbox_or_cast(tmp, target_type, line) if (not src_type.is_unboxed and target_type.is_unboxed) or not is_subtype( src_type, target_type ): diff --git a/mypyc/test-data/irbuild-float.test b/mypyc/test-data/irbuild-float.test index fa9bf1da4800..e3a60852574b 100644 --- a/mypyc/test-data/irbuild-float.test +++ b/mypyc/test-data/irbuild-float.test @@ -475,3 +475,23 @@ def init(n: int) -> None: # narrowing assignments, generate errors here x: float = n # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") y: float = 5 # E: Incompatible value representations in assignment (expression has type "int", variable has type "float") + +[case testFloatCoerceTupleFromIntValues] +from __future__ import annotations + +def f(x: int) -> None: + t: tuple[float, float, float] = (x, 2.5, -7) +[out] +def f(x): + x :: int + r0 :: tuple[int, float, int] + r1 :: int + r2 :: float + r3, t :: tuple[float, float, float] +L0: + r0 = (x, 2.5, -14) + r1 = r0[0] + r2 = CPyFloat_FromTagged(r1) + r3 = (r2, 2.5, -7.0) + t = r3 + return 1 diff --git a/mypyc/test-data/irbuild-i64.test b/mypyc/test-data/irbuild-i64.test index 74eb126b937d..f4755fea2e8b 100644 --- a/mypyc/test-data/irbuild-i64.test +++ b/mypyc/test-data/irbuild-i64.test @@ -650,7 +650,6 @@ def f(x: i64, y: i64) -> Tuple[i64, i64]: return x, y def g() -> Tuple[i64, i64]: - # TODO: Avoid boxing and unboxing return 1, 2 def h() -> i64: @@ -666,13 +665,11 @@ L0: return r0 def g(): r0 :: tuple[int, int] - r1 :: object - r2 :: tuple[int64, int64] + r1 :: tuple[int64, int64] L0: r0 = (2, 4) - r1 = box(tuple[int, int], r0) - r2 = unbox(tuple[int64, int64], r1) - return r2 + r1 = (1, 2) + return r1 def h(): r0 :: tuple[int64, int64] r1, x, r2, y :: int64 @@ -2081,3 +2078,78 @@ L2: r6 = r5.a keep_alive x return r6 + +[case testI64ConvertBetweenTuples] +from __future__ import annotations +from mypy_extensions import i64 + +def f(t: tuple[int, i64, int]) -> None: + tt: tuple[int, i64, i64] = t + +def g(n: int) -> None: + t: tuple[i64, i64] = (1, n) +[out] +def f(t): + t :: tuple[int, int64, int] + r0 :: int + r1 :: int64 + r2 :: int + r3 :: native_int + r4 :: bit + r5, r6 :: int64 + r7 :: ptr + r8 :: c_ptr + r9 :: int64 + r10, tt :: tuple[int, int64, int64] +L0: + r0 = t[0] + r1 = t[1] + r2 = t[2] + r3 = r2 & 1 + r4 = r3 == 0 + if r4 goto L1 else goto L2 :: bool +L1: + r5 = r2 >> 1 + r6 = r5 + goto L3 +L2: + r7 = r2 ^ 1 + r8 = r7 + r9 = CPyLong_AsInt64(r8) + r6 = r9 + keep_alive r2 +L3: + r10 = (r0, r1, r6) + tt = r10 + return 1 +def g(n): + n :: int + r0 :: tuple[int, int] + r1 :: int + r2 :: native_int + r3 :: bit + r4, r5 :: int64 + r6 :: ptr + r7 :: c_ptr + r8 :: int64 + r9, t :: tuple[int64, int64] +L0: + r0 = (2, n) + r1 = r0[1] + r2 = r1 & 1 + r3 = r2 == 0 + if r3 goto L1 else goto L2 :: bool +L1: + r4 = r1 >> 1 + r5 = r4 + goto L3 +L2: + r6 = r1 ^ 1 + r7 = r6 + r8 = CPyLong_AsInt64(r7) + r5 = r8 + keep_alive r1 +L3: + r9 = (1, r5) + t = r9 + return 1 diff --git a/mypyc/test-data/run-floats.test b/mypyc/test-data/run-floats.test index cd36be7b2033..d84681ec658e 100644 --- a/mypyc/test-data/run-floats.test +++ b/mypyc/test-data/run-floats.test @@ -1,6 +1,7 @@ # Test cases for floats (compile and run) [case testFloatOps] +from __future__ import annotations from typing import Any, cast from typing_extensions import Final from testutil import assertRaises, float_vals, FLOAT_MAGIC @@ -329,6 +330,13 @@ def test_undefined_local_var() -> None: with assertRaises(UnboundLocalError, 'local variable "y2" referenced before assignment'): print(y2) +def test_tuples() -> None: + t1: tuple[float, float] = (1.5, 2.5) + assert t1 == tuple([1.5, 2.5]) + n = int() + 5 + t2: tuple[float, float, float, float] = (n, 1.5, -7, -113) + assert t2 == tuple([5.0, 1.5, -7.0, -113.0]) + [case testFloatGlueMethodsAndInheritance] from typing import Any from typing_extensions import Final From 72d9a9cf8b8db68d3f65bde1a3b1cbb648c632f2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 14 Mar 2023 22:10:01 +0000 Subject: [PATCH 2/2] Only run test on 64 bit archictecture --- mypyc/test-data/irbuild-i64.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-i64.test b/mypyc/test-data/irbuild-i64.test index f4755fea2e8b..a18171c41d57 100644 --- a/mypyc/test-data/irbuild-i64.test +++ b/mypyc/test-data/irbuild-i64.test @@ -2079,7 +2079,7 @@ L2: keep_alive x return r6 -[case testI64ConvertBetweenTuples] +[case testI64ConvertBetweenTuples_64bit] from __future__ import annotations from mypy_extensions import i64