Skip to content

Commit 02c9766

Browse files
authored
[mypyc] Use PyList_Check for isinstance(obj, list) (#19416)
Added a primitive to translate `isinstance(obj, list)` to `PyList_Check(obj)` instead of `PyObject_IsInstance(obj, type)`, as the former is faster. Similar primitives can be added for other built-in types, which I plan to do in next PRs. ``` def f(x) -> bool: return isinstance(x, list) def bench(n: int) -> None: for x in range(n): if x % 2 == 0: f(x) else: f([x]) from time import time bench(1000) t0 = time() bench(50 * 1000 * 1000) print(time() - t0) ``` Using the above benchmark, execution time goes from ~1.4s to ~0.97s on my machine.
1 parent 1091321 commit 02c9766

File tree

4 files changed

+73
-33
lines changed

4 files changed

+73
-33
lines changed

mypyc/irbuild/specialize.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Callable, Optional
17+
from typing import Callable, Final, Optional
1818

1919
from mypy.nodes import (
2020
ARG_NAMED,
@@ -89,7 +89,7 @@
8989
dict_setdefault_spec_init_op,
9090
dict_values_op,
9191
)
92-
from mypyc.primitives.list_ops import new_list_set_item_op
92+
from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op
9393
from mypyc.primitives.str_ops import (
9494
str_encode_ascii_strict,
9595
str_encode_latin1_strict,
@@ -546,6 +546,9 @@ def gen_inner_stmts() -> None:
546546
return retval
547547

548548

549+
isinstance_primitives: Final = {"builtins.list": isinstance_list}
550+
551+
549552
@specialize_function("builtins.isinstance")
550553
def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
551554
"""Special case for builtins.isinstance.
@@ -554,11 +557,10 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) ->
554557
there is no need to coerce something to a new type before checking
555558
what type it is, and the coercion could lead to bugs.
556559
"""
557-
if (
558-
len(expr.args) == 2
559-
and expr.arg_kinds == [ARG_POS, ARG_POS]
560-
and isinstance(expr.args[1], (RefExpr, TupleExpr))
561-
):
560+
if not (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]):
561+
return None
562+
563+
if isinstance(expr.args[1], (RefExpr, TupleExpr)):
562564
builder.types[expr.args[0]] = AnyType(TypeOfAny.from_error)
563565

564566
irs = builder.flatten_classes(expr.args[1])
@@ -569,6 +571,15 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) ->
569571
)
570572
obj = builder.accept(expr.args[0], can_borrow=can_borrow)
571573
return builder.builder.isinstance_helper(obj, irs, expr.line)
574+
575+
if isinstance(expr.args[1], RefExpr):
576+
node = expr.args[1].node
577+
if node:
578+
desc = isinstance_primitives.get(node.fullname)
579+
if desc:
580+
obj = builder.accept(expr.args[0])
581+
return builder.primitive_op(desc, [obj], expr.line)
582+
572583
return None
573584

574585

mypyc/primitives/list_ops.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
extra_int_constants=[(0, int_rprimitive)],
5656
)
5757

58+
# isinstance(obj, list)
59+
isinstance_list = function_op(
60+
name="builtins.isinstance",
61+
arg_types=[object_rprimitive],
62+
return_type=bit_rprimitive,
63+
c_function_name="PyList_Check",
64+
error_kind=ERR_NEVER,
65+
)
66+
5867
new_list_op = custom_op(
5968
arg_types=[c_pyssize_t_rprimitive],
6069
return_type=list_rprimitive,

mypyc/test-data/irbuild-lists.test

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -498,29 +498,23 @@ def nested_union(a: Union[List[str], List[Optional[str]]]) -> None:
498498
[out]
499499
def narrow(a):
500500
a :: union[list, int]
501-
r0 :: object
502-
r1 :: i32
503-
r2 :: bit
504-
r3 :: bool
505-
r4 :: list
506-
r5 :: native_int
507-
r6 :: short_int
508-
r7 :: int
501+
r0 :: bit
502+
r1 :: list
503+
r2 :: native_int
504+
r3 :: short_int
505+
r4 :: int
509506
L0:
510-
r0 = load_address PyList_Type
511-
r1 = PyObject_IsInstance(a, r0)
512-
r2 = r1 >= 0 :: signed
513-
r3 = truncate r1: i32 to builtins.bool
514-
if r3 goto L1 else goto L2 :: bool
507+
r0 = PyList_Check(a)
508+
if r0 goto L1 else goto L2 :: bool
515509
L1:
516-
r4 = borrow cast(list, a)
517-
r5 = var_object_size r4
518-
r6 = r5 << 1
510+
r1 = borrow cast(list, a)
511+
r2 = var_object_size r1
512+
r3 = r2 << 1
519513
keep_alive a
520-
return r6
514+
return r3
521515
L2:
522-
r7 = unbox(int, a)
523-
return r7
516+
r4 = unbox(int, a)
517+
return r4
524518
def loop(a):
525519
a :: list
526520
r0 :: short_int

mypyc/test-data/run-lists.test

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ assert not list_in_mixed(object)
466466
assert list_in_mixed(type)
467467

468468
[case testListBuiltFromGenerator]
469-
def test() -> None:
469+
def test_from_gen() -> None:
470470
source_a = ["a", "b", "c"]
471471
a = list(x + "f2" for x in source_a)
472472
assert a == ["af2", "bf2", "cf2"]
@@ -486,12 +486,6 @@ def test() -> None:
486486
f = list("str:" + x for x in source_str)
487487
assert f == ["str:a", "str:b", "str:c", "str:d"]
488488

489-
[case testNextBug]
490-
from typing import List, Optional
491-
492-
def test(x: List[int]) -> None:
493-
res = next((i for i in x), None)
494-
495489
[case testListGetItemWithBorrow]
496490
from typing import List
497491

@@ -537,3 +531,35 @@ def test_sorted() -> None:
537531
assert sorted((2, 1, 3)) == res
538532
assert sorted({2, 1, 3}) == res
539533
assert sorted({2: "", 1: "", 3: ""}) == res
534+
535+
[case testIsInstance]
536+
from copysubclass import subc
537+
def test_built_in() -> None:
538+
assert isinstance([], list)
539+
assert isinstance([1,2,3], list)
540+
assert isinstance(['a','b'], list)
541+
assert isinstance(subc(), list)
542+
assert isinstance(subc([1,2,3]), list)
543+
assert isinstance(subc(['a','b']), list)
544+
545+
assert not isinstance({}, list)
546+
assert not isinstance((), list)
547+
assert not isinstance((1,2,3), list)
548+
assert not isinstance(('a','b'), list)
549+
assert not isinstance(1, list)
550+
assert not isinstance('a', list)
551+
552+
def test_user_defined() -> None:
553+
from userdefinedlist import list
554+
555+
assert isinstance(list(), list)
556+
assert not isinstance([list()], list)
557+
558+
[file copysubclass.py]
559+
from typing import Any
560+
class subc(list[Any]):
561+
pass
562+
563+
[file userdefinedlist.py]
564+
class list:
565+
pass

0 commit comments

Comments
 (0)