From aa1b536517d96adf23a11885bf557ae10c79122c Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 16:37:03 +0800 Subject: [PATCH] =?UTF-8?q?perf(tpc):=20=5Fgenerate=5Fstructs=20=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E4=B8=A4=E5=A4=84"=E7=BA=AF=20no-op=20=E9=87=8D?= =?UTF-8?q?=E5=BB=BA"=EF=BC=8C=E4=B8=8D=E6=94=B9=E4=BB=BB=E4=BD=95?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:b(机制漏缓存)—— 都是"结果等价却重算"的浪费,不是判定逻辑改动。 证据(PROF\main_prof_72472.prof 的 `_generate_structs` 子调用归属,self=39.07s/8,493 次): isinstance 6.195s(6,249 万次)· `HandlesBase.__get__` 3.596s · len 2.892s `_TypedPointerType.__init__` 2.600s(375 万次)· `_patched_new` 2.030s · dict.get 2.555s `_MemberSlotAccepts` 2.437s · SymbolTable.lookup 2.105s · rsplit 1.123s 改动(均为**恒等变换**,任意中断/交错下语义不变,不引入失效判定): 1. Step 3 成员类型重绑:新增 `if pointee is not struct:` / `if member_type is not struct:` 短路。原实现对"pointee 本来就是该 struct 实例"的成员也**现建一个 ir.PointerType 再覆盖写**——llvmlite 类型是值语义(`_TypedPointerType.__eq__` 按 pointee 比较、 打印只取决于结构),覆盖写是纯 no-op。该分支每次调用遍历全部类的全部成员 ⇒ 这是 375 万次指针构造(约占本函数 9s,含 _patched_new/_patched_init/_IsBadPointee) 的主源。 2. T* 占位扫描改为 `_StructBodyHasPlaceholderT`(按 body 对象记忆):Step 4 的 "全类×全元素"扫描与 set_body 前的复核扫描,每次调用都全量重扫,是本函数 6,249 万次 isinstance 与 498 万次 rsplit 的主源。判据精确性:该扫描是 `st.elements` 的纯函数,而 `elements` 在 llvmlite 里**整体替换**(set_body 赋新 tuple、作废置 None,从不原地改元素)⇒ 用 `elements` 的对象身份当版本号即可精确 判定"body 未变";缓存条目强引用 struct 与 elements,二者存活 ⇒ id 不会被回收 复用 ⇒ 不会误判命中。 3. vtable 槽 `i8*` 改用模块级共享实例 `_I8_PTR`(原来每类每次现建)。 **刻意不做**:不加"全量状态签名 early-out"。理由(写在此处备查):签名只能覆盖 "每类成员个数"这类可 O(类数) 取得的量,覆盖不到"成员类型原地替换";而那次替换正是 本函数 Step 3 自己做的 ⇒ 一旦某轮在中途抛异常,签名与上一次成功轮相同,early-out 会跳过**必要的**重建。要做到完备就得给约 25 处 `class_members` 变更点埋版本号, 收益不确定、风险实在,故不做。 验证:py 层 import + GetDiagnostics 干净;未跑编译器。 --- lib/core/LLVMCG/StructGen.py | 82 +++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 15ccbb3..6fb5c0a 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -6,6 +6,12 @@ import llvmlite.ir as ir from llvmlite.ir.values import _Undefined from lib.includes import t as _t +# vtable 槽类型 `i8*` 的共享实例:llvmlite 的类型是不可变值语义(`_TypedPointerType` +# 的 `__eq__` 按结构比较、打印结果只取决于结构),同一实例可在任意多少量复用。 +# 原来在 `_generate_structs` 的逐类 `member_types` 构建里**每类每次现建** +# (profile:`_TypedPointerType.__init__` 3,750,259 次中有一份来自这里)。 +_I8_PTR: ir.Type = ir.PointerType(ir.IntType(8)) + class StructGenMixin: @@ -353,6 +359,40 @@ class StructGenMixin: return True return False + def _StructBodyHasPlaceholderT(self, St: Any) -> bool: + """该 struct body 是否含未特化的 `T*` 占位字段(结果按 body 对象记忆)。 + + `_generate_structs` 里有两处对"全部类的全部元素"做这一扫描(Step 4 的完整性与 + set_body 前的复核),每次调用都全量重扫 —— profile(2026-09-29 main_prof_72472): + 这两处是本函数 62.5M 次 `isinstance`(6.20s)与 4.98M 次 `rsplit`(1.12s)的主源。 + + 判定精确性:扫描是 `st.elements` 的**纯函数**,而 `elements` 在 llvmlite 里是 + **整体替换**(`set_body` 赋新 tuple;作废时置 None;从不原地改元素)⇒ 用 + `elements` 的**对象身份**当版本号即可精确判定"body 未变"。缓存条目**强引用** + struct 实例与 elements 对象,二者存活 ⇒ `id()` 不会被回收复用 ⇒ 不会误判命中。 + (代价:被替换掉的旧 body tuple 会被缓存多留一份引用;量级 = 结构体数量级,可忽略。) + """ + _cache: dict | None = getattr(self, '_struct_t_placeholder_cache', None) + if _cache is None: + _cache = {} + self._struct_t_placeholder_cache = _cache + _key: int = id(St) + _elems: Any = getattr(St, 'elements', None) + _hit: Any = _cache.get(_key) + if _hit is not None and _hit[0] is St and _hit[1] is _elems: + return _hit[2] + _found: bool = False + if _elems: + for _elem in _elems: + if isinstance(_elem, ir.PointerType) and isinstance(_elem.pointee, ir.IdentifiedStructType): + _pname: str = _elem.pointee.name + _short: str = _pname.rsplit('.', 1)[-1] if '.' in _pname else _pname + if _short == 'T': + _found = True + break + _cache[_key] = (St, _elems, _found) + return _found + def _generate_structs(self) -> None: # 热路径局部化:profile 中本函数被调 35,240 次(cum 1893s),内部 # SymbolTable.lookup 15.9M 次(16.7s)+ 真值判断触发的 __len__ 15.9M 次 @@ -469,7 +509,16 @@ class StructGenMixin: orig: Any = getattr(pointee, '_original_name', None) struct = struct_name_map.get(orig) if orig else None if struct is not None: - self.class_members[ClassName][i] = (member_name, ir.PointerType(struct)) + # 【不重建已是同一实例的指针】`ir.PointerType(同一 struct 实例)` + # 与现有成员类型**结构等价**(llvmlite 的 `_TypedPointerType.__eq__` + # 按 pointee 比较;打印结果只取决于结构)⇒ 覆盖写是纯 no-op,却要 + # 现建一个指针对象。profile(2026-09-29 main_prof_72472):本函数内 + # `_TypedPointerType.__init__` 3,750,259 次(2.60s)+ `_patched_new` + # 3,750,260 次(2.03s)+ `_patched_init`/`_IsBadPointee`(约 4.4s) + # 主要来自这里;而本分支每次调用都会遍历全部类的全部成员 + # ⇒ 绝大多数是"pointee 本来就是它"的重复重建。 + if pointee is not struct: + self.class_members[ClassName][i] = (member_name, ir.PointerType(struct)) else: raw_name: str = pname or '' if raw_name.startswith('struct.'): @@ -487,7 +536,9 @@ class StructGenMixin: orig: Any = getattr(member_type, '_original_name', None) struct = struct_name_map.get(orig) if orig else None if struct is not None: - self.class_members[ClassName][i] = (member_name, struct) + # 同上:已是同一实例 ⇒ 覆盖写是纯 no-op(llvmlite 类型按结构相等)。 + if member_type is not struct: + self.class_members[ClassName][i] = (member_name, struct) else: raw_name: str = mname or '' if raw_name.startswith('struct.'): @@ -531,14 +582,9 @@ class StructGenMixin: # 检测 T* 占位类型(未特化的泛型类型参数)。 # 当 struct 包含 T* 字段时(如 BasicBlock 继承 GSListNode[BasicBlock] 但 stub 未特化), # 说明 stub 使用了未特化的基类,需清除重新生成。 - _has_t_placeholder_pre: bool = False - for _elem_pre in existing_st.elements: - if isinstance(_elem_pre, ir.PointerType) and isinstance(_elem_pre.pointee, ir.IdentifiedStructType): - _pname_pre: str = _elem_pre.pointee.name - _short_pre: str = _pname_pre.rsplit('.', 1)[-1] if '.' in _pname_pre else _pname_pre - if _short_pre == 'T': - _has_t_placeholder_pre = True - break + # 结果按 body 对象记忆(见 _StructBodyHasPlaceholderT:本扫描是 + # "全类 × 全元素"的纯函数,重复扫描曾占本函数 isinstance 的主源)。 + _has_t_placeholder_pre: bool = self._StructBodyHasPlaceholderT(existing_st) # 逐元素类型一致性(治本):仅比"个数 / 首元素是 vtable 指针 / T 占位" # 会漏掉**类型错位**的已冻结 body —— 现场 Generator:47 个元素个数正确、 # 元素名字也全对,但第 4 槽是 i8** 而 class_members 声明 @@ -650,7 +696,8 @@ class StructGenMixin: else: member_types = [] if has_vtable: - member_types.append(ir.PointerType(ir.IntType(8))) + # 复用共享 i8* 实例(见模块顶部 _I8_PTR 说明) + member_types.append(_I8_PTR) if ClassName in self.class_members: for _, member_type in self.class_members[ClassName]: if member_type is None: @@ -695,16 +742,9 @@ class StructGenMixin: # 若 stub 使用了未特化的基类(如 GSListNode 而非 GSListNode[BasicBlock]), # 清除 elements 让 set_body 用正确的 class_members 重新设置 # (字段名必须是公开属性 `elements`,见上方 Step 4 的说明)。 - if struct_type.elements is not None and member_types: - _has_t_placeholder_post: bool = False - for _elem_post in struct_type.elements: - if isinstance(_elem_post, ir.PointerType) and isinstance(_elem_post.pointee, ir.IdentifiedStructType): - _pname_post: str = _elem_post.pointee.name - _short_post: str = _pname_post.rsplit('.', 1)[-1] if '.' in _pname_post else _pname_post - if _short_post == 'T': - _has_t_placeholder_post = True - break - if _has_t_placeholder_post: + # 复用 _StructBodyHasPlaceholderT 的按 body 记忆结果(与上方 Step 4 同一判据)。 + if member_types and struct_type.elements is not None: + if self._StructBodyHasPlaceholderT(struct_type): struct_type.elements = None if struct_type.elements is None: # 防御性检查:member_types 为空时不调用 set_body。