From ce612c8e57895e17a4b67eeb2b5a7bf4fa85bb9f Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 12:59:27 +0800 Subject: [PATCH] =?UTF-8?q?perf(lookup):=20=E6=81=A2=E5=A4=8D=E5=90=8D?= =?UTF-8?q?=E5=AD=97=E7=B4=A2=E5=BC=95=E5=8F=AF=E4=BF=A1=E6=9C=AA=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E5=8D=B3=E7=A1=AE=E6=97=A0=E7=9A=84=E7=9F=AD=E8=B7=AF?= =?UTF-8?q?=EF=BC=88Ty=20=E7=B4=A2=E5=BC=95=E4=BF=9D=E6=8C=81=E5=81=9C?= =?UTF-8?q?=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4ed7f39 因 Ty 索引不等价,把名字索引的未命中短路一并回退了,导致 find_struct(8.25M)/lookup_ctypedef(5.64M) 等又回到全表扫描。 经逐处自证不变量后仅恢复名字键索引:名字或键只在注册时写入一次(中和仅置空名,空名查询显式回退扫描);索引在所有写入路径同步维护,判据 Built==Count 失败即失效回退;探测用 live 键且同键条目沿链按注册顺序排列(无空槽夹层)⇒ 可信未命中即原线性扫描亦无所获,短路逐字节等价。 恢复点:StructGenMixin.find_struct、TypeGenCType.lookup_ctypedef、CallGenLeafKit.find_func_in_table/find_func_entry_global、TypeGenCDef.lookup_cdefine_{constant,double,string}、VpiReader._lookup_vpi_param_annots/_lookup_vpi_ret_annot(后者保留泛型特化归一化兜底)。 find_struct_by_type 的 Ty 索引判定保持停用(事故根因);首字符预筛、Function.NameLen/FUNC_SIZE、includes 均未改动。 --- App/lib/Stub/StubGen/VpiReader.vp | 13 ++++---- App/lib/core/Gen/Call/CallGenLeafKit.vp | 13 +++++--- App/lib/core/Gen/StructGenMixin.vp | 25 +++++++++------ App/lib/core/Gen/Type/TypeGenCDef.vp | 42 ++++++++++++++----------- App/lib/core/Gen/Type/TypeGenCType.vp | 17 +++++----- 5 files changed, 65 insertions(+), 45 deletions(-) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 8eddd11..bce892e 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -687,14 +687,14 @@ def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) -> r: str = None ki: int # 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性 - # 扫描首个同名,首注册优先);未命中一律回退下方原线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"可信未命中即确无"的语义已回退。 + # 扫描首个同名,首注册优先);未命中即"确无"(键表只追加、键文本注册后不改写, + # 索引随扩容整体重建且覆盖全表)⇒ 无需再线性扫描;索引不可信 → 回退线性扫描。 hit_p: int = -1 if _g_vpi_param_idx_built == _g_vpi_param_count \ and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0: hit_p = _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN, _g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf) - if hit_p < 0: + else: # 回退原线性扫描:strcmp 判等蕴含首字节相等;键首字节 = src_sha1[0],缓存含 # 多模块条目,首字节不同者必不相等,直接跳过其 strcmp(仅做否定判定,不改命中)。 key_first: int = key_buf[0] @@ -1064,14 +1064,15 @@ def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) i: int # 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性 - # 扫描首个同名,首注册优先);未命中一律回退下方原线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"可信未命中即确无"的语义已回退。 + # 扫描首个同名,首注册优先);未命中即"确无"精确键(键表只追加、键文本注册后 + # 不改写,索引随扩容整体重建且覆盖全表)⇒ 跳过精确线性扫描(下方泛型特化归一化 + # 匹配兜底仍保留,其键形态不同、不依赖精确键命中);索引不可信 → 回退线性扫描。 hit_r: int = -1 if _g_vpi_ret_idx_built == _g_vpi_ret_count \ and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0: hit_r = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) - if hit_r < 0: + else: # 回退原线性扫描:strcmp 判等蕴含首字节相等(键首字节 = src_sha1[0]),跳过首字节不同者。 key_first: int = key_buf[0] for i in range(_g_vpi_ret_count): diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index c375735..8c552bd 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -1051,8 +1051,11 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): t.CVoid(uint64(self.GlobalFuncs) + fi_t * entry_size, t.CPtr)) if fe_t is not None: return fe_t.Func - # 索引未命中一律回退下方原线性扫描(索引只加速命中,未命中行为与未加 - # 索引前逐字节一致);此前"可信未命中即 return None"的语义已回退。 + # 索引可信(Built==GlobalFuncCount | 查询名非空)⇒ 未命中即"确无同名条目": + # 全局表只经 _register_global_func 追加(每次追加即同步索引,失败则置 + # Built=-1),名注册后不再改写 ⇒ owner_sha1 为 None 时原线性扫描等价于 + # "首个同名条目的 Func",未命中必为 None。索引不可信时回退下方线性扫描。 + return None # 单遍扫描(合并原 owner 优先遍 + 通用遍,语义完全一致但省一半遍历): # 扫描中同时记录"首个同裸名条目"作通用兜底;一旦发现本模块条目 # (mangled 名以 "{owner_sha1}." 开头,16 位前缀 + '.')立即返回。 @@ -1273,8 +1276,9 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() # 名索引快速路径:可信(Built==Count)时一次探测返回首个同名条目 - # (= 原线性扫描首个命中);未命中一律回退下方原线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"可信未命中即 return None"已回退。 + # (= 原线性扫描首个命中);可信且查询名非空时未命中即"确无同名条目" + # (全局表只经 _register_global_func 追加并同步索引,名注册后不再改写), + # 直接返回 None;索引不可信 → 回退下方原线性扫描。 if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount \ and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0: gi: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx, @@ -1283,6 +1287,7 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): entry_g: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)( t.CVoid(uint64(self.GlobalFuncs) + gi * entry_size, t.CPtr)) return entry_g + return None for i in range(self.GlobalFuncCount): entry_addr: uint64 = uint64(self.GlobalFuncs) + i * entry_size entry: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr)) diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index dbc193d..30d9ad7 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -784,18 +784,25 @@ class StructGenMixin: VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct)", "STRUCT") stdlib.exit(1) # 名索引快速路径:命中即首注册下标(语义 = 原精确匹配扫描首个命中)。 - # 未命中一律回退下方原精确匹配扫描(索引只加速命中,未命中行为与未加索引 - # 前逐字节一致);精确匹配确无时继续走模块限定名/裸名回退逻辑(不提前 - # return None)。此前"索引可信且查询名非空 ⇒ 未命中即确无、跳过 O(n) 扫描" - # 的语义已回退(一旦判据有未覆盖情形就会把"找得到"变成"找不到")。 fi: int = self._struct_name_idx_find(name) if fi >= 0: return self._get_struct_entry(fi) - for i in range(self.StructCount): - entry: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i) - if entry is not None and entry.Name is not None: - if string.strcmp(entry.Name, name) == 0: - return entry + # 名索引可信(Built==StructCount | 索引存在 | 查询名非空)时,索引未命中即 + # "确无同名条目":条目在注册时即命名并同步入索引,之后名至多被中和为 "" + # (ClassGenDef/ClassGenEnum 置 Name[0]=0),故任何非空查询名的命中项必在 + # 索引探测链上 ⇒ 可跳过下方 O(n) 精确扫描(热点:本扫描占 find_struct 的 + # 全部 strcmp)。索引不可信 / 无索引 / 空名查询时仍回退原线性扫描 + # (正确性保证,非兜底);精确匹配确无时继续走模块限定名/裸名回退逻辑。 + name_idx_trusted: int = 0 + if self.StructNameIdx is not None and self.StructIdxCap > 0 \ + and self.StructIdxBuilt == self.StructCount: + name_idx_trusted = 1 + if name_idx_trusted == 0 or name[0] == '\0': + for i in range(self.StructCount): + entry: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i) + if entry is not None and entry.Name is not None: + if string.strcmp(entry.Name, name) == 0: + return entry # 模块限定名(module.Class):模块可解析时按模块归属精确匹配, # 防止裸名回退抓跨模块同名类。模块未命中时若裸名类唯一(无冲突) # 仍回退,避免误杀未登记模块的既有解析。 diff --git a/App/lib/core/Gen/Type/TypeGenCDef.vp b/App/lib/core/Gen/Type/TypeGenCDef.vp index 23e63cf..835c76a 100644 --- a/App/lib/core/Gen/Type/TypeGenCDef.vp +++ b/App/lib/core/Gen/Type/TypeGenCDef.vp @@ -339,17 +339,19 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return 0 n: sizet = len(self.CDefineNames) i: sizet - # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 - # 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。 + # 名索引快速路径:可信(Built==n | 查询名非空)时直接定位唯一候选下标 + # (= 原扫描首个命中)。CDefine 名注册后不再改写、每次注册都同步入索引 + # (失败即置 Built=-1)⇒ 可信时未命中即"确无",直接返回 0(= 原全表扫描 + # 结果);索引不可用时回退下方原全表线性扫描。 cd_start: sizet = 0 cd_end: sizet = n if name is not None and self.CDefineNameIdxBuilt == int(n) \ and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0: cd_hit: int = self._cdef_idx_find(name) - if cd_hit >= 0: - cd_start = sizet(cd_hit) - cd_end = sizet(cd_hit + 1) + if cd_hit < 0: + return 0 + cd_start = sizet(cd_hit) + cd_end = sizet(cd_hit + 1) for i in range(cd_start, cd_end): nm: str = self.CDefineNames.get(i) if nm is not None and string.strcmp(nm, name) == 0: @@ -379,17 +381,19 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return 0.0 n: sizet = len(self.CDefineNames) i: sizet - # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 - # 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。 + # 名索引快速路径:可信(Built==n | 查询名非空)时直接定位唯一候选下标 + # (= 原扫描首个命中)。CDefine 名注册后不再改写、每次注册都同步入索引 + # (失败即置 Built=-1)⇒ 可信时未命中即"确无",直接返回 0.0(= 原全表扫描 + # 结果);索引不可用时回退下方原全表线性扫描。 cd_start: sizet = 0 cd_end: sizet = n if name is not None and self.CDefineNameIdxBuilt == int(n) \ and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0: cd_hit: int = self._cdef_idx_find(name) - if cd_hit >= 0: - cd_start = sizet(cd_hit) - cd_end = sizet(cd_hit + 1) + if cd_hit < 0: + return 0.0 + cd_start = sizet(cd_hit) + cd_end = sizet(cd_hit + 1) for i in range(cd_start, cd_end): nm: str = self.CDefineNames.get(i) # 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp。 @@ -420,17 +424,19 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return None n: sizet = len(self.CDefineNames) i: sizet - # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 - # 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中, - # 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。 + # 名索引快速路径:可信(Built==n | 查询名非空)时直接定位唯一候选下标 + # (= 原扫描首个命中)。CDefine 名注册后不再改写、每次注册都同步入索引 + # (失败即置 Built=-1)⇒ 可信时未命中即"确无",直接返回 None(= 原全表扫描 + # 结果);索引不可用时回退下方原全表线性扫描。 cd_start: sizet = 0 cd_end: sizet = n if name is not None and self.CDefineNameIdxBuilt == int(n) \ and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0: cd_hit: int = self._cdef_idx_find(name) - if cd_hit >= 0: - cd_start = sizet(cd_hit) - cd_end = sizet(cd_hit + 1) + if cd_hit < 0: + return None + cd_start = sizet(cd_hit) + cd_end = sizet(cd_hit + 1) for i in range(cd_start, cd_end): nm: str = self.CDefineNames.get(i) if nm is not None and string.strcmp(nm, name) == 0: diff --git a/App/lib/core/Gen/Type/TypeGenCType.vp b/App/lib/core/Gen/Type/TypeGenCType.vp index 75e64db..99274b1 100644 --- a/App/lib/core/Gen/Type/TypeGenCType.vp +++ b/App/lib/core/Gen/Type/TypeGenCType.vp @@ -219,17 +219,18 @@ class TypeGenCType(TypeGenBase.TypeGenBase): return None n: sizet = len(self.CTypedefNames) i: sizet - # 名索引快速路径:可信(Built==n)时直接定位唯一候选槽下标(CTypedef 表 - # 注册时按名去重 ⇒ 名唯一,命中即原全表扫描首个命中)。未命中一律回退下方 - # 原全表线性扫描(索引只加速命中,未命中行为与未加索引前逐字节一致); - # 此前"可信未命中即确无、直接 return None"的语义已回退。 + # 名索引快速路径:可信(Built==n)时直接定位唯一候选槽下标。CTypedef 表 + # 注册时按名去重(register_ctypedef 已存在则原地更新类型串、不追加)⇒ 名 + # 唯一且注册后不再改写,索引完整覆盖全表 ⇒ 未命中即"确无",直接返回 None + # (= 原全表扫描结果);索引不可用/不可信 → 回退下方原全表扫描。 if self.CTypedefNameIdxBuilt == int(n) \ and self.CTypedefNameIdx is not None and self.CTypedefNameIdxCap > 0: ti: int = self._ctypedef_idx_find(name) - if ti >= 0: - ts_hit: str = self.CTypedefTypeStrs.get(sizet(ti)) - self.CTypedefFound = 1 - return ts_hit + if ti < 0: + return None + ts_hit: str = self.CTypedefTypeStrs.get(sizet(ti)) + self.CTypedefFound = 1 + return ts_hit for i in range(n): nm: str = self.CTypedefNames.get(i) if nm is not None and string.strcmp(nm, name) == 0: