Skip to content

Commit 23e6072

Browse files
authored
Micro-optimize type indirection visitor (#19460)
Specialize iteration for the concrete types `list` and `tuple`, since this is faster than iterating over an abstract iterable in compiled code. Also avoid constructing many temporary lists. Duplicate some very hot code in the hopes further improving performance slightly, through inlining and possibly also better branch prediction. The approach is similar to what I used in #19459. This is a part of a set of micro-optimizations that improve self check performance by ~5.5%.
1 parent 32752eb commit 23e6072

File tree

1 file changed

+30
-12
lines changed

1 file changed

+30
-12
lines changed

mypy/indirection.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,29 @@ def find_modules(self, typs: Iterable[types.Type]) -> set[str]:
3232
self.modules = set()
3333
self.seen_fullnames = set()
3434
self.seen_aliases = set()
35-
self._visit(typs)
35+
for typ in typs:
36+
self._visit(typ)
3637
return self.modules
3738

38-
def _visit(self, typ_or_typs: types.Type | Iterable[types.Type]) -> None:
39-
typs = [typ_or_typs] if isinstance(typ_or_typs, types.Type) else typ_or_typs
39+
def _visit(self, typ: types.Type) -> None:
40+
if isinstance(typ, types.TypeAliasType):
41+
# Avoid infinite recursion for recursive type aliases.
42+
if typ not in self.seen_aliases:
43+
self.seen_aliases.add(typ)
44+
typ.accept(self)
45+
46+
def _visit_type_tuple(self, typs: tuple[types.Type, ...]) -> None:
47+
# Micro-optimization: Specialized version of _visit for lists
48+
for typ in typs:
49+
if isinstance(typ, types.TypeAliasType):
50+
# Avoid infinite recursion for recursive type aliases.
51+
if typ in self.seen_aliases:
52+
continue
53+
self.seen_aliases.add(typ)
54+
typ.accept(self)
55+
56+
def _visit_type_list(self, typs: list[types.Type]) -> None:
57+
# Micro-optimization: Specialized version of _visit for tuples
4058
for typ in typs:
4159
if isinstance(typ, types.TypeAliasType):
4260
# Avoid infinite recursion for recursive type aliases.
@@ -50,7 +68,7 @@ def _visit_module_name(self, module_name: str) -> None:
5068
self.modules.update(split_module_names(module_name))
5169

5270
def visit_unbound_type(self, t: types.UnboundType) -> None:
53-
self._visit(t.args)
71+
self._visit_type_tuple(t.args)
5472

5573
def visit_any(self, t: types.AnyType) -> None:
5674
pass
@@ -68,7 +86,7 @@ def visit_deleted_type(self, t: types.DeletedType) -> None:
6886
pass
6987

7088
def visit_type_var(self, t: types.TypeVarType) -> None:
71-
self._visit(t.values)
89+
self._visit_type_list(t.values)
7290
self._visit(t.upper_bound)
7391
self._visit(t.default)
7492

@@ -84,10 +102,10 @@ def visit_unpack_type(self, t: types.UnpackType) -> None:
84102
t.type.accept(self)
85103

86104
def visit_parameters(self, t: types.Parameters) -> None:
87-
self._visit(t.arg_types)
105+
self._visit_type_list(t.arg_types)
88106

89107
def visit_instance(self, t: types.Instance) -> None:
90-
self._visit(t.args)
108+
self._visit_type_tuple(t.args)
91109
if t.type:
92110
# Uses of a class depend on everything in the MRO,
93111
# as changes to classes in the MRO can add types to methods,
@@ -98,7 +116,7 @@ def visit_instance(self, t: types.Instance) -> None:
98116
self._visit_module_name(t.type.metaclass_type.type.module_name)
99117

100118
def visit_callable_type(self, t: types.CallableType) -> None:
101-
self._visit(t.arg_types)
119+
self._visit_type_list(t.arg_types)
102120
self._visit(t.ret_type)
103121
if t.definition is not None:
104122
fullname = t.definition.fullname
@@ -107,22 +125,22 @@ def visit_callable_type(self, t: types.CallableType) -> None:
107125
self.seen_fullnames.add(fullname)
108126

109127
def visit_overloaded(self, t: types.Overloaded) -> None:
110-
self._visit(t.items)
128+
self._visit_type_list(list(t.items))
111129
self._visit(t.fallback)
112130

113131
def visit_tuple_type(self, t: types.TupleType) -> None:
114-
self._visit(t.items)
132+
self._visit_type_list(t.items)
115133
self._visit(t.partial_fallback)
116134

117135
def visit_typeddict_type(self, t: types.TypedDictType) -> None:
118-
self._visit(t.items.values())
136+
self._visit_type_list(list(t.items.values()))
119137
self._visit(t.fallback)
120138

121139
def visit_literal_type(self, t: types.LiteralType) -> None:
122140
self._visit(t.fallback)
123141

124142
def visit_union_type(self, t: types.UnionType) -> None:
125-
self._visit(t.items)
143+
self._visit_type_list(t.items)
126144

127145
def visit_partial_type(self, t: types.PartialType) -> None:
128146
pass

0 commit comments

Comments
 (0)