perf(tpc): 声明加载去重 + _parse_llvm_type memo(PROF main_prof_72472 前两名热点)
两项改动同在 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 改动,无需重编)。
This commit is contained in:
1 parent
030d0fd149
commit
3399b44c2d
1 file changed
+143
-11
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user