perf(callgen): text.ll 存在性改属性查询 + 返回类型按模块一次性缓存
第 1 项:_dep_text_ll_exists 原以 fileio.File(MODE.R) 只读打开探测存在性(33,244 次调用,占总 create_file_utf8 46.6%,每次约 57us 的开句柄成本)。改用 GetFileAttributesA 并显式排除目录(对齐 IncludesScanner._PathExistsA 先例),无输出缓冲、零句柄。 第 2 项:_LookupMethodRetTyFromTextLL(29,974 次、约 380,505 tick/次、self 11.41e9 即 8.9%)原每次重开加重读加逐字节重扫整份 text.ll(实测约 2.47 tick/字节,均值 154KB)。新增 PermPool 缓存表(mangled 名 到 返回类型文本):某模块 text.ll 首次成功读入时登记其全部 define 行,同模块后续查询 O(1);命中后用调用方 pool 经 _ParseLLVMRetTyStr 重建类型,与原路径等价。 关键安全点:只存返回类型文本、绝不存会随每文件 reset_to 失效的 LLVMType*;同名只记首次(保持首个匹配优先);仅成功读到文件后才回写,未找到或文件暂不存在绝不缓存(Phase A 顺序生成依赖,负缓存会出错);缓存或分配失败一律回退原扫描。原扫描逻辑一字未改,保留为未命中路径。
This commit is contained in:
1 parent
ce612c8e57
commit
b918321f77
1 file changed
+160
-5
@@ -7,11 +7,13 @@ from stdint import *
|
||||
import ast
|
||||
import llvmlite
|
||||
import memhub
|
||||
import hashtable
|
||||
import string
|
||||
import stdio
|
||||
import stdlib
|
||||
import viperlib
|
||||
import w32.fileio as fileio
|
||||
import w32.win32file as win32file
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
@@ -92,6 +94,14 @@ class CallGenBase:
|
||||
GlobalDefaults: GenTypes.FuncDefaultsEntry | t.CPtr
|
||||
GlobalDefaultsCount: int
|
||||
|
||||
# --- 依赖模块 text.ll「方法返回类型文本」缓存(_LookupMethodRetTyFromTextLL 性能优化) ---
|
||||
# key = text.ll 里 define 的完整 mangled 函数名("{sha1}.{后缀}")→ 返回类型文本。
|
||||
# 表与键/值均 PermPool 分配(跨文件/Phase 存活);sha1 本身是内容哈希 ⇒ 天然
|
||||
# 内容寻址,无需 size/mtime 校验。值只存**文本**(绝不存 LLVMType*——其分配在
|
||||
# 每文件 reset_to 失效的 pool 中);命中后用调用方 pool 重建类型,与原路径等价。
|
||||
# 惰性建表;建表/分配失败即不缓存,回退原扫描(正确性保证,非兜底)。| 不用全局裸表。
|
||||
MethodRetTyMap: hashtable.HashTable | t.CPtr
|
||||
|
||||
def InitCallGen(self) -> int:
|
||||
"""初始化调用 Mixin 字段(子类构造时内联调用,规避 NoVTable 继承符号不转发)"""
|
||||
if self is None:
|
||||
@@ -114,6 +124,7 @@ class CallGenBase:
|
||||
self.GlobalFuncNameIdxBuilt = -1
|
||||
self.GlobalDefaults = None
|
||||
self.GlobalDefaultsCount = 0
|
||||
self.MethodRetTyMap = None
|
||||
return 0
|
||||
|
||||
# ============================================================
|
||||
@@ -1062,6 +1073,127 @@ class CallGenBase:
|
||||
# 无法解析
|
||||
return None
|
||||
|
||||
# ============================================================
|
||||
# _LookupCachedRetTyText - 查「依赖模块 text.ll 方法返回类型文本」持久缓存
|
||||
#
|
||||
# 缓存键 = text.ll 中 define 的完整 mangled 函数名
|
||||
# ("{sha1}.{class}.{method}";bare 模式 "{sha1}.{method}")。sha1 本身是内容
|
||||
# 哈希 ⇒ 天然内容寻址,无需 size/mtime 校验。命中返回返回类型文本(PermPool
|
||||
# 持久),未命中/表未建返回 None(调用方回退原扫描)。
|
||||
# ============================================================
|
||||
def _LookupCachedRetTyText(self, class_sha1: str, class_name: str,
|
||||
method_name: str, use_bare: int) -> str:
|
||||
"""查返回类型文本缓存:命中返回文本,未命中返回 None"""
|
||||
if self.Gen is None or class_sha1 is None or method_name is None:
|
||||
return None
|
||||
ht: hashtable.HashTable | t.CPtr = self.Gen.CallGenBase.MethodRetTyMap
|
||||
if ht is None:
|
||||
return None
|
||||
sha1_len: sizet = string.strlen(class_sha1)
|
||||
cls_len: sizet = 0
|
||||
if class_name is not None:
|
||||
cls_len = string.strlen(class_name)
|
||||
meth_len: sizet = string.strlen(method_name)
|
||||
# 长度公式与 _LookupMethodRetTyFromTextLL 构造 search_buf 时完全一致
|
||||
key_len: sizet = sha1_len + meth_len + 2
|
||||
if use_bare == 0:
|
||||
key_len = sha1_len + cls_len + meth_len + 4
|
||||
key_buf: bytes = stdlib.malloc(key_len)
|
||||
if key_buf is None:
|
||||
return None
|
||||
if use_bare != 0:
|
||||
viperlib.snprintf(key_buf, key_len, "%s.%s", class_sha1, method_name)
|
||||
else:
|
||||
viperlib.snprintf(key_buf, key_len, "%s.%s.%s",
|
||||
class_sha1, class_name, method_name)
|
||||
v: t.CPtr = ht.__getitem__(key_buf)
|
||||
stdlib.free(key_buf)
|
||||
if v is None:
|
||||
return None
|
||||
return (str | t.CPtr)(v)
|
||||
|
||||
# ============================================================
|
||||
# _CacheModuleRetTyTexts - 把某模块 text.ll 全部 define 行的返回类型写入持久缓存
|
||||
#
|
||||
# 仅在 _LookupMethodRetTyFromTextLL 成功读到 text.ll 后调用(未读到文件绝不写
|
||||
# 缓存:Phase A 按文件顺序翻译,依赖模块此遍可能尚未生成 text.ll,未命中必须
|
||||
# 回退原扫描、负结果不可缓存)。键=define 完整 mangled 函数名(深拷贝进
|
||||
# PermPool),值="define " 与 "@" 之间的返回类型文本(深拷贝进 PermPool)。
|
||||
# 同名只记首次出现(与逐行扫描「首个命中即返回」一致);只登记以 "{sha1}."
|
||||
# 开头的本模块 mangled 名(调用方查询键恒为此前缀,外部/内部符号永不命中)。
|
||||
# ============================================================
|
||||
def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes,
|
||||
text_len: sizet) -> None:
|
||||
"""登记 text.ll 全部 define 行到持久缓存(建表/分配失败即不缓存,回退原扫描)"""
|
||||
if self.Gen is None or class_sha1 is None or text_buf is None or text_len == 0:
|
||||
return
|
||||
perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool
|
||||
if perm is None:
|
||||
return
|
||||
ht: hashtable.HashTable | t.CPtr = self.Gen.CallGenBase.MethodRetTyMap
|
||||
if ht is None:
|
||||
ht = hashtable.HashTable(perm)
|
||||
if ht is None:
|
||||
return
|
||||
self.Gen.CallGenBase.MethodRetTyMap = ht
|
||||
sha1_len: sizet = string.strlen(class_sha1)
|
||||
pos: sizet = 0
|
||||
while pos < text_len:
|
||||
ls: sizet = pos
|
||||
le: sizet = pos
|
||||
while le < text_len:
|
||||
if text_buf[le] == '\n':
|
||||
break
|
||||
le += 1
|
||||
ll: sizet = le - ls
|
||||
# define 行判定与 _LookupMethodRetTyFromTextLL 逐字一致
|
||||
if ll >= 7 and text_buf[ls] == 'd' and string.strncmp(text_buf + ls, "define ", 7) == 0:
|
||||
at_pos: sizet = ls + 7
|
||||
while at_pos < ls + ll:
|
||||
if text_buf[at_pos] == '@':
|
||||
break
|
||||
at_pos += 1
|
||||
if at_pos < ls + ll:
|
||||
name_start: sizet = at_pos + 1
|
||||
quoted: int = 0
|
||||
if name_start < ls + ll and text_buf[name_start] == '"':
|
||||
name_start += 1
|
||||
quoted = 1
|
||||
# 定界:带引号名到 '"',裸名到 '('(与查找侧边界判定等价)
|
||||
name_end: sizet = name_start
|
||||
if quoted != 0:
|
||||
while name_end < ls + ll:
|
||||
if text_buf[name_end] == '"':
|
||||
break
|
||||
name_end += 1
|
||||
else:
|
||||
while name_end < ls + ll:
|
||||
if text_buf[name_end] == '(':
|
||||
break
|
||||
name_end += 1
|
||||
name_len: sizet = name_end - name_start
|
||||
# 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀)
|
||||
if name_len > sha1_len + 1:
|
||||
if string.strncmp(text_buf + name_start, class_sha1, sha1_len) == 0 \
|
||||
and text_buf[name_start + sha1_len] == '.':
|
||||
key_buf: bytes = stdlib.malloc(name_len + 1)
|
||||
if key_buf is not None:
|
||||
string.strncpy(key_buf, text_buf + name_start, name_len)
|
||||
key_buf[name_len] = '\0'
|
||||
# 同名首次为准(不被后续同名覆盖,与首个命中一致)
|
||||
if ht.__contains__(key_buf) == 0:
|
||||
rt_len: sizet = at_pos - (ls + 7)
|
||||
kcopy: str = perm.alloc(name_len + 1)
|
||||
tcopy: str = perm.alloc(rt_len + 1)
|
||||
if kcopy is not None and tcopy is not None:
|
||||
string.strncpy(kcopy, text_buf + name_start, name_len)
|
||||
kcopy[name_len] = '\0'
|
||||
string.strncpy(tcopy, text_buf + ls + 7, rt_len)
|
||||
tcopy[rt_len] = '\0'
|
||||
ht.set_str(kcopy, tcopy)
|
||||
stdlib.free(key_buf)
|
||||
pos = le + 1
|
||||
|
||||
# ============================================================
|
||||
# _LookupMethodRetTyFromTextLL - 从依赖模块的 text.ll 查找方法返回类型
|
||||
#
|
||||
@@ -1103,6 +1235,15 @@ class CallGenBase:
|
||||
if sha1_len < 2:
|
||||
return None
|
||||
|
||||
# [按模块一次性缓存·命中] 该模块 text.ll 已被读过一次 ⇒ O(1) 取回函数返回
|
||||
# 类型文本,用**调用方 pool** 重建类型直接返回(与原路径语义等价,不再开
|
||||
# 文件/读盘/逐字节重扫)。未命中 → 走下方原实现;仅在成功读到 text.ll 后
|
||||
# 才回写缓存(依赖模块此遍可能尚未生成,负结果绝不缓存)。
|
||||
cached_text: str = self._LookupCachedRetTyText(
|
||||
class_sha1, class_name, method_name, use_bare)
|
||||
if cached_text is not None:
|
||||
return self._ParseLLVMRetTyStr(pool, cached_text, 0, string.strlen(cached_text))
|
||||
|
||||
# 构造 text.ll 路径 (level=1): {temp_dir}/{sha1[0:2]}/{sha1}.text.ll
|
||||
# 使用 subdir 缓冲区避免 %c 格式化问题(与 StubMerger._sliced_path 一致)
|
||||
path_len: sizet = self.Gen.CallGenBase.TempDirLen + sha1_len + 14
|
||||
@@ -1170,6 +1311,12 @@ class CallGenBase:
|
||||
|
||||
# 在 text.ll 中搜索 define 行包含 search_buf
|
||||
buf_len: sizet = string.strlen(read_buf)
|
||||
|
||||
# [按模块一次性缓存·回写] 已成功读到本模块 text.ll:登记其全部 define 行
|
||||
# (同模块后续查询 O(1) 命中,不再重开/重读/逐字节重扫)。此处不改动下方
|
||||
# 原扫描逻辑(保留为未命中路径)。
|
||||
self._CacheModuleRetTyTexts(class_sha1, read_buf, buf_len)
|
||||
|
||||
pos: sizet = 0
|
||||
result_ty: llvmlite.LLVMType | t.CPtr = None
|
||||
|
||||
@@ -1250,11 +1397,19 @@ class CallGenBase:
|
||||
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll",
|
||||
self.Gen.CallGenBase.TempDir, subdir_buf, sha1)
|
||||
stdlib.free(subdir_buf)
|
||||
tf: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R)
|
||||
exists: int = 0
|
||||
if not tf.closed:
|
||||
tf.close()
|
||||
exists = 1
|
||||
# [廉价存在性判定] 用 GetFileAttributesA(无输出缓冲、不创建文件句柄)替代
|
||||
# 原「只读打开探测」:原实现只为看是否存在却真开句柄(走 create_file_utf8),
|
||||
# 本函数 33,244 次调用占总 create_file_utf8 调用 46.6%。写法对齐同仓既有
|
||||
# IncludesScanner._PathExistsA(该处亦以属性查询替代开文件句柄)。
|
||||
attr: ULONG = win32file.GetFileAttributesA(path_buf)
|
||||
exists: int = 1
|
||||
# INVALID_FILE_ATTRIBUTES = 不存在/不可访问 → 0。
|
||||
if attr == 0xFFFFFFFF:
|
||||
exists = 0
|
||||
# 原实现以只读 CreateFileA 打开:目录会被拒绝(未带 FILE_FLAG_BACKUP_SEMANTICS)
|
||||
# → 0,故此处显式排除目录以保持「是可读常规文件」的等价判定。
|
||||
if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
|
||||
exists = 0
|
||||
stdlib.free(path_buf)
|
||||
return exists
|
||||
|
||||
|
||||
Reference in new issue
Block a user