perf(lookup): 热点线性查找表加开放寻址名索引,O(n) 降为期望 O(1)

VpiReader 参数/返回注解键表(查表 + 写去重共用)、CallGenLeafKit 全局函数表、TypeGenCDef CDefine 表:由线性扫描改为开放寻址索引,对应热点约 117M + 17.8M + 10.5M 次 strcmp。

索引槽只存下标+1(不存键/节点指针),键表 realloc/移动后槽下标仍有效,无悬空指针;可信判据 Built==count,每次注册同步维护,不可信或未命中一律回退原线性扫描(正确性保证)。每次注册占一槽以保持首注册优先(探测链首个键相等者即原首个命中)。CallGenLeafKit 快速路径仅限 owner_sha1 为 None 的全局表查询,保持 owner 优先语义不变。
This commit is contained in:
TermiNexus committed 2026-09-14 00:05:12 +08:00
1 parent a9819e4123
commit 16472a61d9
5 files changed
+589 -24

No files matched your search

+287 -16
View File
@@ -58,11 +58,176 @@ VPI_PARAM_MAX: t.CDefine = 4096
VPI_PARAM_KEY_LEN: t.CDefine = 224
VPI_PARAM_LEN: t.CDefine = 1024
# ============================================================
# 键表开放寻址索引(名 → 槽下标)
#
# 两张键表(返回注解 / 参数注解)都是定长槽数组(每槽 VPI_*_KEY_LEN 字节),
# 插入只追加、从不重排 ⇒ 索引可只存"槽下标"(比存指针更省,且 realloc 移动
# 键表基址后槽下标仍有效)。索引槽数恒为键表容量的 2 倍(2 的幂,负载 < 0.5,
# 线性探测必然终止)。每次注册占一槽(不按键去重):同键条目沿探测链按注册
# 顺序递增排列,查找取链上首个"当前键相等"者 = 原线性扫描首个命中(首注册
# 优先)。可信判据 = Built == Count;不可信/未命中一律回退原线性扫描。
# ============================================================
VPI_RET_IDX_CAP: t.CDefine = 8192
VPI_PARAM_IDX_CAP: t.CDefine = 8192
# 索引哈希常量(32 位 FNV-1a,与 StructGenMixin 范式一致)
VPI_IDX_FNV_OFFSET: t.CDefine = 2166136261
VPI_IDX_FNV_PRIME: t.CDefine = 16777619
VPI_IDX_FNV_MASK: t.CDefine = 4294967295
_g_vpi_param_keys: bytes = None
_g_vpi_param_strs: bytes = None
_g_vpi_param_count: int = 0
_g_vpi_param_cap: int = 0
# 返回注解键表索引(与键表同 malloc 生命周期;Built == Count 才可信)
_g_vpi_ret_idx: bytes = None
_g_vpi_ret_idx_cap: int = 0
_g_vpi_ret_idx_built: int = 0
# 参数注解键表索引(同上)
_g_vpi_param_idx: bytes = None
_g_vpi_param_idx_cap: int = 0
_g_vpi_param_idx_built: int = 0
# ============================================================
# 索引探测/插入/重建(模块级函数,供两张键表共用)
# ============================================================
def _vpi_idx_hash(key: bytes) -> uint64:
"""键串 → 64 位哈希(32 位 FNV-1a,逐字节到 NUL 结束;空键返回偏移基值)"""
h: uint64 = uint64(VPI_IDX_FNV_OFFSET)
if key is None:
return h
i: sizet = 0
while key[i] != '\0':
h = (h ^ uint64(int(key[i]))) * uint64(VPI_IDX_FNV_PRIME)
h = h & uint64(VPI_IDX_FNV_MASK)
i += 1
return h
def _vpi_idx_slot(idx_base: bytes, si: sizet) -> sizet | t.CPtr:
"""取索引第 si 个槽(8 字节,存 slot_index+1;0=空槽)"""
return (sizet | t.CPtr)(t.CVoid(uint64(idx_base) + uint64(si) * 8, t.CPtr))
def _vpi_idx_find(keys: bytes, key_stride: int, idx_base: bytes,
cap: int, key: bytes) -> int:
"""在索引中探测 key,返回命中槽下标(>=0);未命中/索引不可用返回 -1
命中判据是"当前键相等"(strcmp),不能只比哈希。
"""
if keys is None or idx_base is None or cap <= 0 or key is None:
return -1
h: uint64 = _vpi_idx_hash(key)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
i: int = 0
while i < cap:
slot: sizet | t.CPtr = _vpi_idx_slot(idx_base, si)
sv: sizet = slot[0]
if sv == 0:
return -1
se: int = int(sv) - 1
ek: bytes = keys + se * key_stride
if string.strcmp(ek, key) == 0:
return se
si = (si + 1) & mask
i += 1
return -1
def _vpi_idx_insert(idx_base: bytes, cap: int, slot_index: int, key: bytes) -> int:
"""把 (key → slot_index) 记入索引(每次注册占一槽,不去重);0=成功 / 1=失败"""
if idx_base is None or cap <= 0 or key is None:
return 1
h: uint64 = _vpi_idx_hash(key)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
i: int = 0
while i < cap:
slot: sizet | t.CPtr = _vpi_idx_slot(idx_base, si)
if slot[0] == 0:
slot[0] = sizet(slot_index + 1)
return 0
si = (si + 1) & mask
i += 1
return 1
def _vpi_rebuild_ret_idx(new_cap: int) -> int:
"""随返回注解键表扩容重建索引(槽数=new_cap,2 的幂);0=可信 / 1=不可信
槽存下标非指针:键表 realloc 后仅基址变,槽下标与键内容均未变,故重建
只需从新基址逐条重放现有 count 个键即可(顺序不变 ⇒ 首注册优先不变)。
"""
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
if new_cap <= 0:
_g_vpi_ret_idx = None
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
return 1
new_idx: bytes = None
if _g_vpi_ret_idx is None:
new_idx = stdlib.malloc(new_cap * 8)
else:
new_idx = stdlib.realloc(_g_vpi_ret_idx, new_cap * 8)
if new_idx is None:
_g_vpi_ret_idx = None
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
return 1
string.memset(new_idx, 0, sizet(new_cap) * 8)
_g_vpi_ret_idx = new_idx
_g_vpi_ret_idx_cap = new_cap
fail: int = 0
ri: int
for ri in range(_g_vpi_ret_count):
if _vpi_idx_insert(new_idx, new_cap, ri,
_g_vpi_ret_keys + ri * VPI_RET_KEY_LEN) != 0:
fail = 1
break
if fail != 0:
_g_vpi_ret_idx_built = -1
return 1
_g_vpi_ret_idx_built = _g_vpi_ret_count
return 0
def _vpi_rebuild_param_idx(new_cap: int) -> int:
"""随参数注解键表扩容重建索引(同 _vpi_rebuild_ret_idx);0=可信 / 1=不可信"""
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
if new_cap <= 0:
_g_vpi_param_idx = None
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
return 1
new_idx: bytes = None
if _g_vpi_param_idx is None:
new_idx = stdlib.malloc(new_cap * 8)
else:
new_idx = stdlib.realloc(_g_vpi_param_idx, new_cap * 8)
if new_idx is None:
_g_vpi_param_idx = None
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
return 1
string.memset(new_idx, 0, sizet(new_cap) * 8)
_g_vpi_param_idx = new_idx
_g_vpi_param_idx_cap = new_cap
fail: int = 0
ri: int
for ri in range(_g_vpi_param_count):
if _vpi_idx_insert(new_idx, new_cap, ri,
_g_vpi_param_keys + ri * VPI_PARAM_KEY_LEN) != 0:
fail = 1
break
if fail != 0:
_g_vpi_param_idx_built = -1
return 1
_g_vpi_param_idx_built = _g_vpi_param_count
return 0
# ============================================================
# _cache_vpi_return_annots_text - 从 vpi_buf 文本直接提取方法返回注解
@@ -76,6 +241,7 @@ _g_vpi_param_cap: int = 0
def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
"""从 vpi_buf 文本提取方法返回注解字符串(stdlib 持久)"""
global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count, _g_vpi_ret_cap
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
if vpi_buf is None or src_sha1 is None:
return 0
if _g_vpi_ret_keys is None:
@@ -86,6 +252,16 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
string.memset(_g_vpi_ret_keys, 0, VPI_RET_MAX * VPI_KEY_LEN)
string.memset(_g_vpi_ret_strs, 0, VPI_RET_MAX * VPI_RET_LEN)
_g_vpi_ret_cap = VPI_RET_MAX
# 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1),
# 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。
_g_vpi_ret_idx = stdlib.malloc(VPI_RET_IDX_CAP * 8)
if _g_vpi_ret_idx is not None:
string.memset(_g_vpi_ret_idx, 0, VPI_RET_IDX_CAP * 8)
_g_vpi_ret_idx_cap = VPI_RET_IDX_CAP
_g_vpi_ret_idx_built = _g_vpi_ret_count
else:
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
sha1_len: sizet = string.strlen(src_sha1)
buf_len: sizet = string.strlen(vpi_buf)
cur_cls: bytes = None
@@ -189,10 +365,23 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
# 去重:同一 key 已存在则跳过(同文件可能被多次加载)
dup_key: int = 0
dki: int
for dki in range(_g_vpi_ret_count):
if string.strcmp(_g_vpi_ret_keys + dki * VPI_KEY_LEN, key_buf) == 0:
# 索引快速路径:索引可信(Built==Count)时一次探测判定
# 重复(命中即"首个注册优先"= 原线性扫描首个同名);
# 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
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:
if _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN,
_g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) >= 0:
dup_key = 1
break
else:
# 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节
# 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。
dup_first: int = key_buf[0]
for dki in range(_g_vpi_ret_count):
ent_d: bytes = _g_vpi_ret_keys + dki * VPI_KEY_LEN
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
dup_key = 1
break
grow_ok: int = 1
if dup_key == 0 and _g_vpi_ret_count >= _g_vpi_ret_cap:
grow_ok = _grow_vpi_ret_cache()
@@ -202,6 +391,14 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
ret_src, rtype_len)
(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0'
_g_vpi_ret_count += 1
# 同步索引:仅当插入前可信(Built==Count-1,含扩容
# 重建后)且插入成功;否则标记不可信(查找回退扫描)。
if _g_vpi_ret_idx_built == _g_vpi_ret_count - 1 \
and _vpi_idx_insert(_g_vpi_ret_idx, _g_vpi_ret_idx_cap,
_g_vpi_ret_count - 1, key_buf) == 0:
_g_vpi_ret_idx_built = _g_vpi_ret_count
else:
_g_vpi_ret_idx_built = -1
stdlib.free(key_buf)
stdlib.free(mname_buf)
pos = le + 1
@@ -225,6 +422,7 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
"""从 vpi_buf 文本提取函数/方法参数注解字符串(stdlib 持久)"""
global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_count, _g_vpi_param_cap
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
if vpi_buf is None or src_sha1 is None:
return 0
if _g_vpi_param_keys is None:
@@ -235,6 +433,16 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
string.memset(_g_vpi_param_keys, 0, VPI_PARAM_MAX * VPI_PARAM_KEY_LEN)
string.memset(_g_vpi_param_strs, 0, VPI_PARAM_MAX * VPI_PARAM_LEN)
_g_vpi_param_cap = VPI_PARAM_MAX
# 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1),
# 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。
_g_vpi_param_idx = stdlib.malloc(VPI_PARAM_IDX_CAP * 8)
if _g_vpi_param_idx is not None:
string.memset(_g_vpi_param_idx, 0, VPI_PARAM_IDX_CAP * 8)
_g_vpi_param_idx_cap = VPI_PARAM_IDX_CAP
_g_vpi_param_idx_built = _g_vpi_param_count
else:
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
sha1_len: sizet = string.strlen(src_sha1)
buf_len: sizet = string.strlen(vpi_buf)
cur_cls: bytes = None
@@ -386,10 +594,23 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf)
dup_key: int = 0
dki: int
for dki in range(_g_vpi_param_count):
if string.strcmp(_g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN, key_buf) == 0:
# 索引快速路径:索引可信(Built==Count)时一次探测判定
# 重复(命中即"首个注册优先"= 原线性扫描首个同名);
# 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
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:
if _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN,
_g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf) >= 0:
dup_key = 1
break
else:
# 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节
# 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。
dup_first: int = key_buf[0]
for dki in range(_g_vpi_param_count):
ent_d: bytes = _g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
dup_key = 1
break
grow_ok: int = 1
if dup_key == 0 and _g_vpi_param_count >= _g_vpi_param_cap:
grow_ok = _grow_vpi_param_cache()
@@ -399,6 +620,14 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
plist, plist_pos)
(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN)[plist_pos] = '\0'
_g_vpi_param_count += 1
# 同步索引:仅当插入前可信(Built==Count-1,含扩容
# 重建后)且插入成功;否则标记不可信(查找回退扫描)。
if _g_vpi_param_idx_built == _g_vpi_param_count - 1 \
and _vpi_idx_insert(_g_vpi_param_idx, _g_vpi_param_idx_cap,
_g_vpi_param_count - 1, key_buf) == 0:
_g_vpi_param_idx_built = _g_vpi_param_count
else:
_g_vpi_param_idx_built = -1
stdlib.free(key_buf)
stdlib.free(mname_buf)
stdlib.free(plist)
@@ -428,6 +657,8 @@ def _grow_vpi_param_cache() -> int:
_g_vpi_param_keys = new_keys
_g_vpi_param_strs = new_strs
_g_vpi_param_cap = new_cap
# 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂)
_vpi_rebuild_param_idx(new_cap * 2)
return 1
@@ -455,10 +686,24 @@ def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) ->
viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name)
r: str = None
ki: int
for ki in range(_g_vpi_param_count):
if string.strcmp(_g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN, key_buf) == 0:
r = _g_vpi_param_strs + ki * VPI_PARAM_LEN
break
# 索引快速路径:索引可信(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)
else:
# 快速路径:strcmp 判等蕴含首字节相等;键首字节 = src_sha1[0],缓存含多模块
# 条目,首字节不同者必不相等,直接跳过其 strcmp(仅做否定判定,不改变命中)。
key_first: int = key_buf[0]
for ki in range(_g_vpi_param_count):
ent_p: bytes = _g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN
if ent_p[0] == key_first and string.strcmp(ent_p, key_buf) == 0:
hit_p = ki
break
if hit_p >= 0:
r = _g_vpi_param_strs + hit_p * VPI_PARAM_LEN
stdlib.free(key_buf)
return r
@@ -597,6 +842,8 @@ def _grow_vpi_ret_cache() -> int:
_g_vpi_ret_keys = new_keys
_g_vpi_ret_strs = new_strs
_g_vpi_ret_cap = new_cap
# 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂)
_vpi_rebuild_ret_idx(new_cap * 2)
return 1
@@ -622,9 +869,14 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
# 该模块已有缓存条目 → 跳过(幂等)
if _g_vpi_ret_keys is not None and _g_vpi_ret_count > 0:
pre_i: int
# 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等),
# src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。
s0: int = src_sha1[0]
s1: int = src_sha1[1]
for pre_i in range(_g_vpi_ret_count):
pre_key: bytes = _g_vpi_ret_keys + pre_i * VPI_KEY_LEN
if string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
if pre_key[0] == s0 and pre_key[1] == s1 and \
string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
return 1
# 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题)
td_len: sizet = string.strlen(temp_dir)
@@ -680,9 +932,14 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
# 该模块已有参数缓存条目 → 跳过(幂等)
if _g_vpi_param_keys is not None and _g_vpi_param_count > 0:
pre_i: int
# 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等),
# src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。
s0: int = src_sha1[0]
s1: int = src_sha1[1]
for pre_i in range(_g_vpi_param_count):
pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN
if string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
if pre_key[0] == s0 and pre_key[1] == s1 and \
string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
return 1
# 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题)
td_len: sizet = string.strlen(temp_dir)
@@ -805,10 +1062,24 @@ def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str
else:
viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name)
i: int
for i in range(_g_vpi_ret_count):
if string.strcmp(_g_vpi_ret_keys + i * VPI_KEY_LEN, key_buf) == 0:
stdlib.free(key_buf)
return _g_vpi_ret_strs + i * VPI_RET_LEN
# 索引快速路径:索引可信(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)
else:
# 快速路径:strcmp 判等蕴含首字节相等(键首字节 = src_sha1[0]),首字节不同者跳过。
key_first: int = key_buf[0]
for i in range(_g_vpi_ret_count):
ent_r: bytes = _g_vpi_ret_keys + i * VPI_KEY_LEN
if ent_r[0] == key_first and string.strcmp(ent_r, key_buf) == 0:
hit_r = i
break
if hit_r >= 0:
stdlib.free(key_buf)
return _g_vpi_ret_strs + hit_r * VPI_RET_LEN
# 泛型特化归一化匹配:list[AST|t.CPtr].get → list[T].get 模板缓存。
# 命中后把模板注解中的类型参数名(如 T)替换为实参(AST|t.CPtr)再返回。
# 不依赖模板类已注册/特化已发生(base.vp 可能先于 list 模块翻译)。