From 3399b44c2d04797693020f99e7cfcff48c538347 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 16:31:29 +0800 Subject: [PATCH] =?UTF-8?q?perf(tpc):=20=E5=A3=B0=E6=98=8E=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=8E=BB=E9=87=8D=20+=20=5Fparse=5Fllvm=5Ftype=20memo?= =?UTF-8?q?=EF=BC=88PROF=20main=5Fprof=5F72472=20=E5=89=8D=E4=B8=A4?= =?UTF-8?q?=E5=90=8D=E7=83=AD=E7=82=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两项改动同在 HandlesImports.py,合并提交:都属"重复重跑"型浪费,且第二项是第一项 放大链的下游。 【一】声明加载按依赖签名去重(类别 a:编译器缺陷) 证据:_TryLoadClassMembersFromPyi 142,888 次 → 6,683 次落到 _EmitExternalClassDeclLlvm (cum 103.1s)→ _generate_structs 8,059 次(cum 128.7s)→ _TryLoadStructFromStub 88,970 次 → _parse_llvm_type 909,382 次;对照模块声明发射仅 320 次 ⇒ 绝大多数是 同状态的重复发射。 根因:guard 只在"表非空 **且已登记已展平**"时短路,而"父类是泛型特化/始终不可得" 的类永远不会被 `Gen._members_flattened` 登记 ⇒ guard 永不命中 ⇒ 每次重入都重跑整套 `_EmitExternalClassDeclLlvm`(其内部又触发 _generate_structs → _TryLoadStructFromStub → 本方法,即每轮 4 帧的环)。 修复:新增 `_ClassMembersReloadSignature`(纯读)收录"重跑能否产生新结果"的全部输入 ——全局进度(已登记类数/已展平类数)、本类成员表长度与展平登记、struct body 元素数、 各父类的成员表长度与展平登记;签名不变即直接短路,依赖一变立刻放行(不破坏"与父类/ struct 就绪顺序无关"的设计目标;与 EnsureClassMembersFlattened 的依赖签名同一思路)。 要点:键含 source_sha1(同名类分属不同模块);**先登记再发射**(否则环上第一层重入 还会再发一次,去重被推迟一层);发射抛异常时撤销登记(保住"失败可重试",不让失败态 被签名冻结);force=True 完全绕过。 【二】`_parse_llvm_type` memo + 基础类型表提升为模块常量(类别 b:漏缓存) 证据:该函数被调 1,689,809 次(cum 46.7s),`type_map` 字典每次调用现建 ⇒ 字典字面量 1.69M 次、`IntType.__new__` 9.09M 次;每个 `X*` 还要新建 `ir.PointerType` ⇒ 全程序 `_TypedPointerType.__init__` 4,596,463 次(≈12–15s)主要来自这里。 修复:拆 memo 包装 + `_parse_llvm_type_impl`(原实现逐字搬移,内部递归仍走包装); memo 只缓存**与 Gen 状态无关**的结果(新增 `_IsGenIndependentLlvmType`:类型图不含 `IdentifiedStructType` 且非 None——含具名结构体的结果绑定 `Gen.structs` 实例且有 泛型特化副作用,None 表示"此刻未加载",二者都不可缓存,理由写进注释防以后被放宽); `_LLVM_PRIMITIVE_TYPE_MAP` 提升为模块级常量(llvmlite 中这些类型无状态、IntType 按宽度缓存同一实例,跨调用/跨 Gen 共享安全)。 验证:py 层 smoke test 覆盖 16 种类型(含 struct / struct* / [4 x struct] / {i32,struct} / fn(struct) / fn->struct* 组合),判定全部符合预期,模块可正常 import。 未跑编译器(无 includes 改动,无需重编)。 --- lib/core/Handles/HandlesImports.py | 154 ++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 11 deletions(-) diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index 1da2993..33fe841 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -33,6 +33,21 @@ _MARKER_BASE_NAMES: frozenset[str] = frozenset({ 'Exception', 'Enum', }) +# LLVM 基础类型表(模块级常量):`_parse_llvm_type_impl` 原先把这份字典**每次调用现建** +# (profile 2026-09-29:本函数 1,689,809 次调用 ⇒ 字典字面量 1.69M 次、 +# `IntType.__new__` 9.09M 次)。这些基础类型在 llvmlite 里无状态且 IntType 按宽度 +# 缓存同一实例,跨调用/跨 Gen 共享安全 ⇒ 提升为模块常量,表意逐字不变。 +_LLVM_PRIMITIVE_TYPE_MAP: dict[str, ir.Type] = { + 'void': ir.VoidType(), + 'i1': ir.IntType(1), + 'i8': ir.IntType(8), + 'i16': ir.IntType(16), + 'i32': ir.IntType(32), + 'i64': ir.IntType(64), + 'float': ir.FloatType(), + 'double': ir.DoubleType(), +} + def _HasRealParentClass(Node: ast.ClassDef) -> bool: """类是否声明了**真实父类**(排除 Object/CVTable/NoVTable/CEnum/... 标记基类)。""" @@ -187,6 +202,10 @@ class ImportHandle(BaseHandle): # 重新加载,导致函数未注册到 __parser.py 的 Gen.functions(NamedExpr bug 根因)。 self._loaded_pyi_paths: set[str] = set() self._emitted_module_decls: set[tuple] = set() + # `_parse_llvm_type` 的结果 memo(键 = (类型串, source_sha1, create_structs))。 + # 只缓存**与 Gen 状态无关**的结果,见 `_parse_llvm_type` 的判据;因此跨文件 + # 复用是安全的(i8* 之类纯字面类型与哪个 Gen 无关),不随 `_reset_file_cache` 清空。 + self._llvm_type_memo: dict[tuple, ir.Type] = {} def _reset_file_cache(self) -> None: self._struct_Load_cache.clear() @@ -3138,9 +3157,83 @@ class ImportHandle(BaseHandle): Gen.class_parents.pop(class_name, None) if hasattr(Gen, 'class_parent'): Gen.class_parent.pop(class_name, None) - self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1) + # 去重登记(force=True 时不登记,见下);在 try 之外先绑定, + # 便于发射失败时统一撤销登记。 + _sigmap: dict | None = None + _sig_key: tuple | None = None + _sig: tuple | None = None + if not force: + # 【稳定态去重 · 依赖签名】guard(上方)只在"表非空 **且已登记已展平**" + # 时短路;而"父类是泛型特化/始终不可得"的类**永远不会**被登记为已展平 + # ⇒ guard 永不命中 ⇒ 每次重入都重跑整套 `_EmitExternalClassDeclLlvm` + # (其内部又触发 _generate_structs → _TryLoadStructFromStub → 本方法, + # 形成每轮 4 帧的环)。profile(2026-09-29 main_prof_72472)实测: + # _TryLoadClassMembersFromPyi 142,888 次 → 6,683 次落到 _EmitExternalClassDeclLlvm + # (cum 103.1s)→ _generate_structs 8,059 次(cum 128.7s) + # → _TryLoadStructFromStub 88,970 次 → _parse_llvm_type 909,382 次 + # 即"环上重复重跑"而非"首次发射"是主开销。 + # 依赖签名(与 EnsureClassMembersFlattened 的"(struct 元素数, 成员表长度) + # 签名"同一思路):签名不变 ⇒ 上一次发射已经把该状态下能填的都填完了, + # 重跑是纯 no-op,直接短路。**只在签名变化时才允许重跑** ⇒ 与"父类/struct + # 就绪顺序无关"这条设计目标不冲突(依赖一变,签名就变,立刻放行)。 + # 保守量:全局"已登记类数 / 已展平类数"——声明加载只要有任何进展就会变, + # 因此不会把"别的类补齐后本类该重跑"的情形漏掉。 + # 键含 source_sha1:同名类可能分属不同模块,发射是 per-(sha1, 类名) 的。 + _sigmap: dict | None = getattr(Gen, '_class_members_reload_sig', None) + if _sigmap is None: + _sigmap = {} + Gen._class_members_reload_sig = _sigmap + _sig_key: tuple = (source_sha1, class_name) + _sig: tuple = self._ClassMembersReloadSignature(Gen, class_name) + if _sigmap.get(_sig_key) == _sig: + return True + # 先登记、再发射:**登记必须在发射之前**,否则环上第一个内层重入 + # (发射进行中,状态已变)仍会看到"无签名"而再发一次,去重被推迟一层; + # 先登记后内层重入即可在同状态下直接短路(环深 2 层即收敛)。 + _sigmap[_sig_key] = _sig + try: + self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1) + except BaseException: + # 发射失败(fail-fast 或将被上层 except 吞掉):撤销本次签名登记, + # 保证"失败可重试"这一既有语义不被去重挡死——否则失败态会被签名 + # 永久冻结(正是本仓反复踩过的"残缺表冻结"类缺陷)。 + if _sigmap is not None and _sigmap.get(_sig_key) == _sig: + del _sigmap[_sig_key] + raise return True + def _ClassMembersReloadSignature(self, Gen: LlvmGeneratorMixin, class_name: str) -> tuple: + """`_TryLoadClassMembersFromPyi` 重跑的依赖签名(纯读,不产生副作用)。 + + 收录"重跑能否产生新结果"的全部输入: + - 全局进度:`len(class_members)` / `len(_members_flattened)` + - 本类自身:成员表长度、是否已登记已展平、struct body 元素数(None ⇒ -1) + - 各父类:成员表长度 + 是否已展平(父类从 `class_parents` / `class_parent` 取; + 两者都缺时用空元组 —— 此时本类依赖尚未建立,签名仍稳定,不影响收敛) + """ + _parents: list = list((getattr(Gen, 'class_parents', {}) or {}).get(class_name) or []) + if not _parents: + _p1: str | None = (getattr(Gen, 'class_parent', {}) or {}).get(class_name) + if _p1: + _parents = [_p1] + _flat: set = getattr(Gen, '_members_flattened', None) or set() + _st: object = Gen.structs.get(class_name) + _st_len: int = -1 + if isinstance(_st, ir.IdentifiedStructType) and _st.elements is not None: + _st_len = len(_st.elements) + _parent_sig: tuple = tuple( + (_p, len(Gen.class_members.get(_p, []) or []), _p in _flat) + for _p in _parents + ) + return ( + len(Gen.class_members), + len(_flat), + len(Gen.class_members.get(class_name, []) or []), + class_name in _flat, + _st_len, + _parent_sig, + ) + def _BuildScalarConstant(self, value_node: ast.AST, target_type: ir.Type, Gen: LlvmGeneratorMixin) -> ir.Constant | None: """Build a scalar constant value from AST node, delegated to unified method.""" return self.Trans._BuildScalarConstantUnified(value_node, target_type, Gen) @@ -3656,21 +3749,60 @@ class ImportHandle(BaseHandle): is_variadic = '...' in params_str return ir.FunctionType(ret_type, param_types, var_arg=is_variadic) + @staticmethod + def _IsGenIndependentLlvmType(T: ir.Type) -> bool: + """该 llvm 类型是否**不含具名结构体**(⇒ 结果与 `Gen.structs` 状态无关,可缓存)。 + + 具名结构体(`IdentifiedStructType`)由 `Gen._get_or_create_struct` 创建、 + 会被 `_generate_structs` 清除重建(instance 与注册表状态绑定),且 + `create_structs=False` 分支返回的 None 表示"此刻未加载"(状态依赖) + ⇒ 这两类结果一律不进 memo。 + """ + if isinstance(T, ir.IdentifiedStructType): + return False + if isinstance(T, ir.PointerType): + return ImportHandle._IsGenIndependentLlvmType(T.pointee) + if isinstance(T, ir.ArrayType): + return ImportHandle._IsGenIndependentLlvmType(T.element) + if isinstance(T, ir.VectorType): + return ImportHandle._IsGenIndependentLlvmType(T.element) + if isinstance(T, ir.LiteralStructType): + return all(ImportHandle._IsGenIndependentLlvmType(_e) for _e in T.elements) + if isinstance(T, ir.FunctionType): + return (ImportHandle._IsGenIndependentLlvmType(T.return_type) + and all(ImportHandle._IsGenIndependentLlvmType(_a) for _a in T.args)) + return True + def _parse_llvm_type(self, type_str: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None, create_structs: bool = True) -> ir.Type | None: + """`_parse_llvm_type_impl` 的 memo 包装(只缓存与 Gen 状态无关的结果)。 + + profile(2026-09-29 main_prof_72472):本函数被调 1,689,809 次 + (cum 46.7s,其中 909,382 次来自 `_TryLoadStructFromStub`、57,525 次来自 + `_LoadDeclarationsFromStubLlvm`、683,127 次是自递归),并且每次 `X*` 都要 + 新建 `ir.PointerType` —— 全程序 `_TypedPointerType.__init__` 4,596,463 次 + (≈12–15s)主要来自这里。而 `i8*`/`i32`/`[N x i8]` 这些**纯字面类型串**的 + 解析结果与任何 Gen 状态无关,重复解析纯属浪费 ⇒ 加 memo。 + """ + _key_str: str = type_str.strip() + _key: tuple = (_key_str, source_sha1, create_structs) + _memo: dict = self._llvm_type_memo + try: + return _memo[_key] + except KeyError: + pass + _res: ir.Type | None = self._parse_llvm_type_impl( + _key_str, Gen, source_sha1=source_sha1, create_structs=create_structs) + if _res is not None and ImportHandle._IsGenIndependentLlvmType(_res): + _memo[_key] = _res + return _res + + def _parse_llvm_type_impl(self, type_str: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None, create_structs: bool = True) -> ir.Type | None: """解析 LLVM 类型字符串""" type_str = type_str.strip() - type_map = { - 'void': ir.VoidType(), - 'i1': ir.IntType(1), - 'i8': ir.IntType(8), - 'i16': ir.IntType(16), - 'i32': ir.IntType(32), - 'i64': ir.IntType(64), - 'float': ir.FloatType(), - 'double': ir.DoubleType(), - } + # 模块级常量(原为每调用重建的字典字面量,见 _LLVM_PRIMITIVE_TYPE_MAP 注释) + type_map: dict[str, ir.Type] = _LLVM_PRIMITIVE_TYPE_MAP if type_str in type_map: return type_map[type_str]