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:
1 parent
3399b44c2d
commit
aa1b536517
1 file changed
+61
-21
@@ -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。
|
||||
|
||||
Reference in new issue
Block a user