perf(tpc): _generate_structs 去掉两处"纯 no-op 重建",不改任何判定语义

类别: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 干净;未跑编译器。
This commit is contained in:
TermiNexus committed 2026-09-29 16:37:03 +08:00
1 parent 3399b44c2d
commit aa1b536517
1 file changed
+61 -21
+61 -21
View File
@@ -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。