From 68d180e7182cda1b851bdd7e2d617d84bed5be5d Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 17 Aug 2026 23:27:44 +0800 Subject: [PATCH] =?UTF-8?q?perf(handles):=20=E8=B7=A8=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=B1=BB=E5=A3=B0=E6=98=8E=E5=88=86=E7=B1=BB/=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=9F=A5=E6=89=BE=E7=BC=93=E5=AD=98=EF=BC=8C=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=20=5FEmitExternalClassDeclLlvm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增类级 _extern_class_classify_cache((sha1,class)->分类元组)与 _extern_class_decl_cache((sha1,class)->pyi ClassDef 节点):bases/ decorator 分类遍历提取为 _ClassifyExternalClass 纯函数并缓存, _TryLoadClassMembersFromPyi 节点查找改走索引避免跨任务重复线性扫描 pyi_tree.body(8214 次调用热点之一)。Gen 依赖部分(字段/方法/struct 注册)保持每 Gen 执行,不做整体跳过。 --- lib/core/Handles/HandlesImports.py | 205 +++++++++++++++++------------ 1 file changed, 124 insertions(+), 81 deletions(-) diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index 57bf388..92c29ec 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -27,6 +27,14 @@ class ImportHandle(BaseHandle): # (原 609 次调用 × ~100 stub 文件 × ~50 行 = 5.5M 正则匹配 → 一次扫描 ~5000 匹配) _stub_index: dict[str, list[tuple[str, str, str, bool]]] = {} _stub_index_signature: tuple[int, int] | None = None # (output_stub_count, temp_stub_count) + # 类级缓存:跨 worker 任务共享(同一进程内,worker 单线程无竞争)。 + # 键均为 (source_sha1, class_name),不同文件同名类(不同 sha1)不冲突。 + # classify:_EmitExternalClassDeclLlvm 开头的 bases/decorator_list 分类遍历结果 + # (与 Gen 无关的纯函数计算,同一 (sha1, class) 跨任务重复调用时跳过遍历)。 + # decl:pyi 树中的 ClassDef 节点引用(pyi 树索引,pyi 树由 _pyi_cache 类级缓存 + # 保证进程内唯一),避免 _TryLoadClassMembersFromPyi 跨任务反复线性扫描 body。 + _extern_class_classify_cache: dict[tuple[str, str], tuple] = {} + _extern_class_decl_cache: dict[tuple[str, str], ast.ClassDef] = {} def __init__(self, translator: "Translator") -> None: super().__init__(translator) @@ -1370,78 +1378,111 @@ class ImportHandle(BaseHandle): if ReexportName2 not in Gen.functions: Gen.functions[ReexportName2] = func + def _ClassifyExternalClass(self, Node: ast.ClassDef) -> tuple: + """遍历 Node.bases/decorator_list 做类别判断(纯函数、与 Gen 无关)。 + + 结果由 _EmitExternalClassDeclLlvm 按 (source_sha1, class_name) 类级缓存, + 同一 worker 进程内多个任务对同一跨模块类重复调用时跳过本遍历。 + 返回 (IsGenericClass, IsCenum, IsRenum, IsCpythonObject, IsCVTable, + IsNoVTable, IsPacked)。 + + PEP 695 泛型类(如 GSListNode[T])特殊:跨模块继承展平需要从泛型基类 + 获取字段(如 Next),否则子类(如 Value(GSListNode[Value]))的 + class_members 会缺少继承字段;但泛型类的方法声明必须跳过(方法参数 + 类型 T 会被解析为 opaque struct,LLVM 不允许 opaque struct 作为值参数)。 + """ + IsGenericClass: bool = hasattr(Node, 'type_params') and bool(Node.type_params) + IsCenum = False + IsRenum = False + IsCpythonObject = False + IsCVTable = False + IsNoVTable = False + if Node.bases: + for base in Node.bases: + if getattr(base, 'attr', None): + if base.attr == 'REnum': + IsRenum = True + break + elif base.attr == 'CEnum' or base.attr == 'Enum': + IsCenum = True + break + elif getattr(base, 'id', None): + if base.id == 'REnum': + IsRenum = True + break + elif base.id == 'CEnum' or base.id == 'Enum': + IsCenum = True + break + if not IsCenum and not IsRenum and getattr(Node, 'decorator_list', None): + for decorator in Node.decorator_list: + if isinstance(decorator, ast.Attribute): + if getattr(decorator.value, 'id', None) == 't': + if decorator.attr == 'Object': + IsCpythonObject = True + elif decorator.attr == 'CVTable': + IsCVTable = True + elif decorator.attr == 'NoVTable': + IsNoVTable = True + elif decorator.attr == 'REnum': + IsRenum = True + elif decorator.attr == 'CEnum' or decorator.attr == 'Enum': + IsCenum = True + elif isinstance(decorator, ast.Call): + if isinstance(decorator.func, ast.Attribute): + if getattr(decorator.func.value, 'id', None) == 't': + if decorator.func.attr == 'Object': + IsCpythonObject = True + elif decorator.func.attr == 'CVTable': + IsCVTable = True + elif decorator.func.attr == 'NoVTable': + IsNoVTable = True + elif isinstance(decorator.func, ast.Name): + if decorator.func.id == 'Object': + IsCpythonObject = True + elif decorator.func.id == 'CVTable': + IsCVTable = True + elif decorator.func.id == 'NoVTable': + IsNoVTable = True + IsPacked = False + if getattr(Node, 'decorator_list', None): + for decorator in Node.decorator_list: + if isinstance(decorator, ast.Call): + if isinstance(decorator.func, ast.Attribute): + if getattr(decorator.func.value, 'id', None) == 'c' and decorator.func.attr == 'Attribute': + for arg in decorator.args: + if isinstance(arg, ast.Attribute): + if isinstance(arg.value, ast.Attribute): + if getattr(arg.value.value, 'id', None) == 't' and arg.value.attr == 'attr' and arg.attr == 'packed': + IsPacked = True + return (IsGenericClass, IsCenum, IsRenum, IsCpythonObject, + IsCVTable, IsNoVTable, IsPacked) + def _EmitExternalClassDeclLlvm(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin, module_name: str | None = None, actual_module_name: str | None = None, source_sha1: str | None = None) -> None: ClassName = Node.name - # PEP 695 泛型类(如 GSListNode[T])不再完全跳过:跨模块继承展平需要从泛型基类 - # 获取字段(如 Next),否则子类(如 Value(GSListNode[Value]))的 class_members - # 会缺少继承字段,导致 struct body 字段数不正确 → GEP 索引错位 → 崩溃。 - # 但泛型类的方法声明必须跳过:方法参数类型 T 会被解析为 opaque struct, - # LLVM 不允许 opaque struct 作为值参数(invalid type for function argument)。 - IsGenericClass: bool = hasattr(Node, 'type_params') and bool(Node.type_params) - IsCenum = False - IsRenum = False - IsCpythonObject = False - IsCVTable = False - IsNoVTable = False - if Node.bases: - for base in Node.bases: - if getattr(base, 'attr', None): - if base.attr == 'REnum': - IsRenum = True - break - elif base.attr == 'CEnum' or base.attr == 'Enum': - IsCenum = True - break - elif getattr(base, 'id', None): - if base.id == 'REnum': - IsRenum = True - break - elif base.id == 'CEnum' or base.id == 'Enum': - IsCenum = True - break - if not IsCenum and not IsRenum and getattr(Node, 'decorator_list', None): - for decorator in Node.decorator_list: - if isinstance(decorator, ast.Attribute): - if getattr(decorator.value, 'id', None) == 't': - if decorator.attr == 'Object': - IsCpythonObject = True - elif decorator.attr == 'CVTable': - IsCVTable = True - elif decorator.attr == 'NoVTable': - IsNoVTable = True - elif decorator.attr == 'REnum': - IsRenum = True - elif decorator.attr == 'CEnum' or decorator.attr == 'Enum': - IsCenum = True - elif isinstance(decorator, ast.Call): - if isinstance(decorator.func, ast.Attribute): - if getattr(decorator.func.value, 'id', None) == 't': - if decorator.func.attr == 'Object': - IsCpythonObject = True - elif decorator.func.attr == 'CVTable': - IsCVTable = True - elif decorator.func.attr == 'NoVTable': - IsNoVTable = True - elif isinstance(decorator.func, ast.Name): - if decorator.func.id == 'Object': - IsCpythonObject = True - elif decorator.func.id == 'CVTable': - IsCVTable = True - elif decorator.func.id == 'NoVTable': - IsNoVTable = True + # Compute source_sha1 up front (moved from mid-function) so it can serve as + # the key of the class-level classification cache. Prefer the explicit + # source_sha1 passed by _TryLoadClassMembersFromPyi (module_name lookup may + # fail when the current module does not import the defining module directly). + if not source_sha1: + if actual_module_name and hasattr(Gen, 'ModuleSha1Map') and actual_module_name in Gen.ModuleSha1Map: + source_sha1 = Gen.ModuleSha1Map[actual_module_name] + elif module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map: + source_sha1 = Gen.ModuleSha1Map[module_name] + # Class-level classification cache: the bases/decorator_list walk below is pure + # Gen-independent computation. Repeated calls on the same (source_sha1, class_name) + # across worker tasks (same process) skip the walk. When source_sha1 is None + # (lookup failed) neither read nor write the cache, to avoid mismatching + # same-named classes from different modules under a shared None key. + _classify_key = (source_sha1, ClassName) if source_sha1 else None + _cached_classify = self._extern_class_classify_cache.get(_classify_key) if _classify_key else None + if _cached_classify is None: + _cached_classify = self._ClassifyExternalClass(Node) + if _classify_key is not None: + self._extern_class_classify_cache[_classify_key] = _cached_classify + (IsGenericClass, IsCenum, IsRenum, IsCpythonObject, + IsCVTable, IsNoVTable, IsPacked) = _cached_classify if ClassName.startswith('__') and not IsCpythonObject and not IsCenum and not IsRenum: return - IsPacked = False - if getattr(Node, 'decorator_list', None): - for decorator in Node.decorator_list: - if isinstance(decorator, ast.Call): - if isinstance(decorator.func, ast.Attribute): - if getattr(decorator.func.value, 'id', None) == 'c' and decorator.func.attr == 'Attribute': - for arg in decorator.args: - if isinstance(arg, ast.Attribute): - if isinstance(arg.value, ast.Attribute): - if getattr(arg.value.value, 'id', None) == 't' and arg.value.attr == 'attr' and arg.attr == 'packed': - IsPacked = True if IsPacked: Gen.class_packed.add(ClassName) if IsCpythonObject: @@ -1609,14 +1650,8 @@ class ImportHandle(BaseHandle): Gen.class_member_bitoffsets[ClassName] = {} if ClassName not in Gen.class_methods: Gen.class_methods[ClassName] = [] - # 先计算 source_sha1,确保首次 _TryLoadStructFromStub 调用就能使用正确的 sha1 - # 优先使用传入的 source_sha1(来自 _TryLoadClassMembersFromPyi 的直接调用, - # 当前模块未直接导入定义模块时 module_name 查找会失败,需依赖传入的 source_sha1) - if not source_sha1: - if actual_module_name and hasattr(Gen, 'ModuleSha1Map') and actual_module_name in Gen.ModuleSha1Map: - source_sha1 = Gen.ModuleSha1Map[actual_module_name] - elif module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map: - source_sha1 = Gen.ModuleSha1Map[module_name] + # source_sha1 已在函数开头统一计算(作为分类缓存键),此处直接使用。 + # 注意:REnum 分支(上方)会按 actual_module_name 优先重新推导并已 return。 # 提前注册 struct:使用导入的 source_sha1 创建 opaque struct, # 避免后续 _TryLoadStructFromStub 触发嵌套编译时,StructGen 用本地 sha1 # 创建缺少基类字段(如 GSListNode.Next)的本地类型定义。 @@ -2211,14 +2246,22 @@ class ImportHandle(BaseHandle): # module_name 反查失败时(当前模块未直接导入定义模块), # 仍需注册类成员和 vtable 信息。传 source_sha1 让 _EmitExternalClassDeclLlvm # 正确设置 class_sha1_map 和注册 vtable。 + # 类级 pyi 树节点索引:同一 (source_sha1, class_name) 的 ClassDef 节点在进程内 + # 唯一(_pyi_cache 以 pyi_path 为键只 parse 一次),避免跨 worker 任务反复 + # 线性扫描 pyi_tree.body 查找同一定义类。 found = False - for node in pyi_tree.body: - if isinstance(node, ast.ClassDef) and node.name == class_name: - self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1) - found = True - break + node = self._extern_class_decl_cache.get((source_sha1, class_name)) + if node is not None: + found = True + else: + for node in pyi_tree.body: + if isinstance(node, ast.ClassDef) and node.name == class_name: + self._extern_class_decl_cache[(source_sha1, class_name)] = node + found = True + break if not found: return + self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1) if class_name in Gen.class_members and len(Gen.class_members[class_name]) > 0: return return