perf(hotspot): 计时串行化并改为自身耗时(exclusive)统计
--hotspot-sites 的每函数计时原为裸 rdtsc 的 inclusive 耗时,导致 _parse_* 一条调用链各项数值几乎相同、无法定位自身成本,且短函数测量不可信。 新增 _EmitReadTimestamp:x86_64 用 lfence 加 readcyclecounter 串行化读(业界通行的 lfence;rdtsc),非 x86_64 编译期降级为裸 readcyclecounter;入口与每个 ret 前共用。选 intrinsic 组合而非内联汇编,避免约束串转义与渲染风险。 用深度栈实现自身耗时:全局 __fhs_tdepth 与 [512 x i64] __fhs_tstart/__fhs_tchild,self = inc 减去本帧内被插桩子调用 inc 之和;递归靠每调用独立 slot 与深度索引天然正确;深度溢出或终结指令非 ret 的块明确跳过不记账(仅计进入次数),不伪造数值。入口段用 select 而非条件分支,避免改动 CFG。 hotspot_time.txt 行为 函数名 self inc 进入次数(仍按 self 降序),段头注明单位 tick 与 self 口径;hotspot_sites.txt 格式保持不变;帮助文本同步更新。未插桩的被调方(含 libc)耗时计入调用者 self,属该方法固有性质并已注明。
This commit is contained in:
1 parent
fc5a6134ca
commit
84d91d53e5
3 files changed
+190
-68
No files matched your search
@@ -149,7 +149,7 @@ def main() -> None:
|
||||
parser.add_argument('--clear-cache', action='store_true', help='清除 .transpyc_cache 全局缓存(.vpi/.stub.ll/.doc.json)')
|
||||
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
|
||||
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按累计耗时降序写入同目录 hotspot_time.txt')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按自身耗时(self)降序写入同目录 hotspot_time.txt,行为 `<函数名> <self> <inc> <进入次数>`(单位 tick,x86_64 上为序列化 lfence; rdtsc);self 已扣除被插桩子调用的 inc,但不含未插桩被调方(间接调用、strcmp/memcpy 等无定义外部函数),它们计入调用者的 self')
|
||||
parser.add_argument('--hotspot-callees', default=None, help='--hotspot-sites 的白名单被调函数基名(逗号分隔),默认内存/字符串族 strcmp,strncmp,strchr,strlen,strstr,memcpy,memset,memmove,strcpy,strcat')
|
||||
parser.add_argument('--hotspot-sites-all', action='store_true', help='--hotspot-sites 对全部直接 call 点插桩(忽略白名单;体积/开销大,用于深度排查)')
|
||||
|
||||
|
||||
@@ -47,8 +47,11 @@ Usage: python TransPyC.py -f InputFile -o OutputFile [-wh header_files] [-debug
|
||||
[--hotspot-sites [SitesFile]] [--hotspot-callees a,b,c] [--hotspot-sites-all]
|
||||
-h: Specify helper files (C or Python) to help identify structs, functions, variables, and ptrs
|
||||
--hotspot-sites: 统计各调用点真实运行次数(Ctrl+C 退出时写入 SitesFile,默认 hotspot_sites.txt,
|
||||
按计数降序),并为每个函数定义加计时器(累计耗时与进入次数按耗时降序写入同目录
|
||||
hotspot_time.txt;耗时单位为 tick = @llvm.readcyclecounter() 计数)
|
||||
按计数降序),并为每个函数定义加计时器:自身耗时 self、包含式耗时 inc 与进入次数,
|
||||
按 self 降序写入同目录 hotspot_time.txt(行为 `<函数名> <self> <inc> <进入次数>`);
|
||||
单位 tick(x86_64 上为序列化的 lfence; rdtsc 计数)。self 已扣除本函数内被插桩
|
||||
子调用的 inc,但**不含未插桩的被调方**(间接调用、strcmp/memcpy 等无定义的外部
|
||||
函数、深度溢出跳过的调用),这些仍计入调用者的 self
|
||||
--hotspot-callees: --hotspot-sites 的白名单被调函数基名(逗号分隔,默认内存/字符串族)
|
||||
--hotspot-sites-all: --hotspot-sites 对全部直接 call 点插桩(忽略白名单,体积/开销大)
|
||||
'''
|
||||
|
||||
+184
-65
@@ -1062,68 +1062,174 @@ class BaseGenMixin:
|
||||
"""
|
||||
return os.path.join(os.path.dirname(sites_path), 'hotspot_time.txt')
|
||||
|
||||
def _EmitHotspotTimeCounters(
|
||||
self,
|
||||
) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]]:
|
||||
"""为模块内每个函数定义安装"每函数累计耗时"计时器(--hotspot-sites 附带)。
|
||||
# 深度栈容量:512。超出该深度的调用不记账(self/inc 均不计),但仍在
|
||||
# __fhs_ftime_enter_<N> 里计入进入次数——溢出是"拿不到数值",明确跳过而非伪造。
|
||||
_HotspotDepth: int = 512
|
||||
|
||||
计时方式(inclusive / 包含式,含被调方耗时):在函数入口用一次
|
||||
@llvm.readcyclecounter() 取起始 tick 存入**本调用帧**的 alloca;在该函数
|
||||
每个 ret 终结指令之前再取一次 tick,把"本次调用耗时"累加进该函数专属的
|
||||
internal i64 __fhs_ftime_<N>;同时用 internal i64 __fhs_ftime_enter_<N>
|
||||
统计该函数被进入次数,便于交叉校验。
|
||||
def _EmitReadTimestamp(self, b: ir.IRBuilder, name: str = '') -> ir.Value:
|
||||
"""发射一次**串行化**时间戳读取,返回 i64 tick(入口与每个 ret 前共用)。
|
||||
|
||||
返回记录列表 (累计耗时全局, 进入次数全局, 函数名),顺序即 N 的编号顺序。
|
||||
|
||||
计时原语:@llvm.readcyclecounter()(LLVM 内建 intrinsic,x86 上降为 rdtsc、
|
||||
arm64 上降为 cntvct_el0),无 libc/Win32 依赖、无需额外链接;精度为 1 个
|
||||
tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。
|
||||
|
||||
插桩规则与跳过条件:
|
||||
- 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;计数器一律
|
||||
internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。
|
||||
- 入口 alloca 置于入口块最前:每次调用都会执行,故递归/重入时各调用帧
|
||||
各自持有起始 tick,差值互不串扰;本属性为"包含式"耗时(递归/嵌套会
|
||||
把内层耗时重复计入外层,需结合进入次数解读)。
|
||||
- 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的
|
||||
块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值。
|
||||
- 累加只做 load/sub/add/store 与读取计时原语,不改变返回值/参数/控制流。
|
||||
串行化方案:在 x86_64 目标上先取一次内存屏障 @llvm.x86.sse2.lfence 再读
|
||||
@llvm.readcyclecounter()(该 intrinsic 在 x86_64 后端降为 rdtsc)。理由:
|
||||
- lfence 在 Intel/AMD 上对取指与访存有序列化效果,能阻止 rdtsc 与前后访存
|
||||
乱序(裸 rdtsc 只保证自身 edx:eax 原子,不保证与其它指令的顺序),这是
|
||||
"lfence; rdtsc" 这一业界通行的序列化读法;
|
||||
- 用 LLVM intrinsic 组合而非 ir.InlineAsm:无内联汇编约束串的转义/渲染风险,
|
||||
也不依赖 Clang 风格 asm 约束是否被 llvmlite 正确渲染,可移植性更好。
|
||||
非 x86_64 目标(如 aarch64/arm/i386)降级为**裸** @llvm.readcyclecounter():
|
||||
lfence 是 x86(SSE2) 专有 intrinsic,在非 x86 目标上不存在、编译期即不可用,
|
||||
故此处按目标三元组在**编译期**决定是否发射 lfence(不在运行期静默退化)。
|
||||
"""
|
||||
i64: ir.IntType = ir.IntType(64)
|
||||
if bool(self._platform_info.get('is_x86_64')):
|
||||
lfence: ir.Function = self._DeclareExternalFunc(
|
||||
'llvm.x86.sse2.lfence', ir.FunctionType(ir.VoidType(), []))
|
||||
b.call(lfence, [])
|
||||
rcc: ir.Function = self._DeclareExternalFunc(
|
||||
'llvm.readcyclecounter', ir.FunctionType(i64, []))
|
||||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]] = []
|
||||
return b.call(rcc, [], name=name)
|
||||
|
||||
def _EmitHotspotTimeCounters(
|
||||
self,
|
||||
) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]]:
|
||||
"""为模块内每个函数定义安装"自身耗时(self) + 包含式耗时(inc) + 进入次数"计时器。
|
||||
|
||||
计时原语(串行化读):见 _EmitReadTimestamp(x86_64 上 lfence; rdtsc,
|
||||
其它目标降级为裸 rdtsc)。无 libc/Win32 依赖、无需额外链接;精度为 1 个
|
||||
tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。
|
||||
|
||||
exclusive(自身耗时)算法——深度栈:
|
||||
- 全局 internal i64 __fhs_tdepth:当前插桩深度,初值 0;
|
||||
- 全局 internal [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化,
|
||||
DEPTH = _HotspotDepth = 512):按深度索引的"本帧起始 tick"与"本帧内被
|
||||
插桩子调用 inclusive 之和";另有一个 internal i64 抖动槽 __fhs_tscratch,
|
||||
用于把"不应记账"的写入导向一个永不读取的位置(等价于不写,但不分支)。
|
||||
- 入口(每个被插桩函数 F,索引 N):
|
||||
t0 = 串行化读时间戳()
|
||||
d = load __fhs_tdepth
|
||||
ok = d < DEPTH
|
||||
__fhs_tstart[ok ? d : 0] = t0 (溢出时写抖动槽)
|
||||
__fhs_tchild[ok ? d : 0] = 0 (溢出时写抖动槽)
|
||||
__fhs_tdepth = ok ? d + 1 : d (溢出时不推进深度)
|
||||
slot = ok ? d : -1 存进该次调用的 alloca(每调用一个独立栈槽)
|
||||
__fhs_ftime_enter_N += 1 (溢出也计进入次数)
|
||||
说明:slot 是"本帧独占的深度索引",递归/重入时各帧各占一层,天然正确;
|
||||
因为本帧在其返回前独享 slot,故 ret 时用 __fhs_tstart[slot] 即本帧 t0
|
||||
(无需再单独保存 t0 到 alloca,避免出现无人读取的死存储)。
|
||||
- 每个 ret 之前(slot = load):
|
||||
inok = slot >= 0;idx = inok ? slot : 0
|
||||
t1 = 串行化读时间戳()
|
||||
inc = t1 - __fhs_tstart[idx] (本次 inclusive)
|
||||
self = inc - __fhs_tchild[idx] (减去本次内被插桩子调用的 inc 之和)
|
||||
__fhs_fself_N += inok ? self : 0 (新增:自身耗时)
|
||||
__fhs_finc_N += inok ? inc : 0 (保留:包含式耗时)
|
||||
__fhs_tdepth = inok ? slot : load __fhs_tdepth (回到本帧深度)
|
||||
if slot > 0(含用抖动槽等价实现): __fhs_tchild[slot-1] += inc
|
||||
(把本次 inc 记到父帧的 child 累加器)
|
||||
溢出帧(slot=-1)不记账、也不写入父帧 child;其墙钟时间仍落在父帧的
|
||||
inc 里,因而会计入父帧的 **self**(见下方说明)。
|
||||
|
||||
返回记录列表 (自身耗时全局, 包含式耗时全局, 进入次数全局, 函数名),顺序即
|
||||
N 的编号顺序。落盘由 _CreateHotspotTimeHandler 完成(按 self 降序)。
|
||||
|
||||
固有性质(务必据此解读数值):**未插桩的被调方**(间接调用、无定义体的外部
|
||||
函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用)
|
||||
其耗时不会被本方法从调用者 self 中扣除,一律计入**调用者自身 self**。故 self
|
||||
是"该函数自身指令 + 未插桩子调用"的墙钟时间,只是比 inc 更贴近自身成本。
|
||||
|
||||
插桩规则与跳过条件:
|
||||
- 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;计数器/深度栈
|
||||
一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。
|
||||
- 入口代码置于入口块最前(无终结指令,不改变 CFG);条件写入用 select 实现,
|
||||
不新增基本块、不修改任何既有分支/终结指令。
|
||||
- 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的
|
||||
块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值(数量见
|
||||
报告,如需可另行统计)。
|
||||
- 累加只做 load/sub/add/store/select 与读取计时原语,不改变返回值/参数/控制流。
|
||||
"""
|
||||
depth: int = self._HotspotDepth
|
||||
i32: ir.IntType = ir.IntType(32)
|
||||
i64: ir.IntType = ir.IntType(64)
|
||||
c0_32: ir.Constant = ir.Constant(i32, 0)
|
||||
c0_64: ir.Constant = ir.Constant(i64, 0)
|
||||
c1_64: ir.Constant = ir.Constant(i64, 1)
|
||||
cneg1_64: ir.Constant = ir.Constant(i64, -1)
|
||||
depth_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName('__fhs_tdepth'))
|
||||
depth_gv.linkage = 'internal'
|
||||
depth_gv.initializer = c0_64
|
||||
arr_ty: ir.ArrayType = ir.ArrayType(i64, depth)
|
||||
tstart_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tstart'))
|
||||
tstart_gv.linkage = 'internal'
|
||||
tstart_gv.initializer = ir.Constant(arr_ty, None)
|
||||
tchild_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tchild'))
|
||||
tchild_gv.linkage = 'internal'
|
||||
tchild_gv.initializer = ir.Constant(arr_ty, None)
|
||||
scratch_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName('__fhs_tscratch'))
|
||||
scratch_gv.linkage = 'internal'
|
||||
scratch_gv.initializer = c0_64
|
||||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]] = []
|
||||
for func in [f for f in self.module.functions if getattr(f, 'blocks', None)]:
|
||||
if func.name.startswith('__fhs_'):
|
||||
continue
|
||||
idx: int = len(records)
|
||||
sum_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_{idx}'))
|
||||
sum_gv.linkage = 'internal'
|
||||
sum_gv.initializer = ir.Constant(i64, 0)
|
||||
self_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_fself_{idx}'))
|
||||
self_gv.linkage = 'internal'
|
||||
self_gv.initializer = c0_64
|
||||
inc_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_finc_{idx}'))
|
||||
inc_gv.linkage = 'internal'
|
||||
inc_gv.initializer = c0_64
|
||||
enter_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_enter_{idx}'))
|
||||
enter_gv.linkage = 'internal'
|
||||
enter_gv.initializer = ir.Constant(i64, 0)
|
||||
# 入口:alloca 起始 tick → 取 tick 存入 → 进入次数 +1(置于入口块最前)
|
||||
enter_gv.initializer = c0_64
|
||||
# 入口:取 t0 → 压深度栈 → 记录本帧 slot → 进入次数 +1(置于入口块最前)
|
||||
entry: ir.Block = func.entry_basic_block
|
||||
eb: ir.IRBuilder = ir.IRBuilder(entry)
|
||||
eb.position_at_start(entry)
|
||||
start_ptr: ir.Value = eb.alloca(i64, name=f'fhs_ftime_start_{idx}')
|
||||
eb.store(eb.call(rcc, [], name=f'fhs_ftime_begin_{idx}'), start_ptr)
|
||||
eb.store(eb.add(eb.load(enter_gv), ir.Constant(i64, 1)), enter_gv)
|
||||
# 每个 ret 之前:tick - 起始 = 本次调用耗时,累加进 __fhs_ftime_<N>
|
||||
slot_ptr: ir.Value = eb.alloca(i64, name=f'fhs_t_slot_{idx}')
|
||||
t0: ir.Value = self._EmitReadTimestamp(eb, name=f'fhs_t_t0_{idx}')
|
||||
d: ir.Value = eb.load(depth_gv, name=f'fhs_t_depth_{idx}')
|
||||
ok: ir.Value = eb.icmp_unsigned('<', d, ir.Constant(i64, depth), name=f'fhs_t_ok_{idx}')
|
||||
safe_idx: ir.Value = eb.select(ok, d, c0_64, name=f'fhs_t_idx_{idx}')
|
||||
eb.store(t0, eb.select(
|
||||
ok, eb.gep(tstart_gv, [c0_32, safe_idx]), scratch_gv))
|
||||
eb.store(c0_64, eb.select(
|
||||
ok, eb.gep(tchild_gv, [c0_32, safe_idx]), scratch_gv))
|
||||
eb.store(eb.select(ok, eb.add(d, c1_64), d), depth_gv)
|
||||
eb.store(eb.select(ok, d, cneg1_64), slot_ptr)
|
||||
eb.store(eb.add(eb.load(enter_gv), c1_64), enter_gv)
|
||||
# 每个 ret 之前:算 self/inc,回退深度,累加父帧 child
|
||||
for block in func.blocks:
|
||||
term: Any = block.terminator
|
||||
if not isinstance(term, ir.Ret):
|
||||
continue
|
||||
rb: ir.IRBuilder = ir.IRBuilder(block)
|
||||
rb.position_before(term)
|
||||
delta: ir.Value = rb.sub(
|
||||
rb.call(rcc, [], name=f'fhs_ftime_end_{idx}'),
|
||||
rb.load(start_ptr), name=f'fhs_ftime_delta_{idx}')
|
||||
rb.store(rb.add(rb.load(sum_gv), delta), sum_gv)
|
||||
records.append((sum_gv, enter_gv, func.name))
|
||||
slot: ir.Value = rb.load(slot_ptr, name=f'fhs_t_slotv_{idx}')
|
||||
inok: ir.Value = rb.icmp_signed('>=', slot, c0_64, name=f'fhs_t_inok_{idx}')
|
||||
t1: ir.Value = self._EmitReadTimestamp(rb, name=f'fhs_t_t1_{idx}')
|
||||
safe_slot: ir.Value = rb.select(inok, slot, c0_64, name=f'fhs_t_slotidx_{idx}')
|
||||
inc_v: ir.Value = rb.sub(
|
||||
t1, rb.load(rb.gep(tstart_gv, [c0_32, safe_slot])),
|
||||
name=f'fhs_t_inc_{idx}')
|
||||
child_v: ir.Value = rb.load(
|
||||
rb.gep(tchild_gv, [c0_32, safe_slot]), name=f'fhs_t_child_{idx}')
|
||||
self_v: ir.Value = rb.sub(inc_v, child_v, name=f'fhs_t_self_{idx}')
|
||||
rb.store(rb.add(rb.load(self_gv), rb.select(inok, self_v, c0_64)), self_gv)
|
||||
rb.store(rb.add(rb.load(inc_gv), rb.select(inok, inc_v, c0_64)), inc_gv)
|
||||
rb.store(rb.select(inok, slot, rb.load(depth_gv)), depth_gv)
|
||||
pok: ir.Value = rb.icmp_signed('>', slot, c0_64, name=f'fhs_t_pok_{idx}')
|
||||
p_idx: ir.Value = rb.select(pok, rb.sub(slot, c1_64), c0_64)
|
||||
p_ptr: ir.Value = rb.gep(tchild_gv, [c0_32, p_idx])
|
||||
p_target: ir.Value = rb.select(pok, p_ptr, scratch_gv)
|
||||
rb.store(rb.add(rb.load(p_target), inc_v), p_target)
|
||||
records.append((self_gv, inc_gv, enter_gv, func.name))
|
||||
return records
|
||||
|
||||
def _CreateHotspotSitesHandler(
|
||||
@@ -1315,20 +1421,23 @@ class BaseGenMixin:
|
||||
def _CreateHotspotTimeHandler(
|
||||
self,
|
||||
path: str,
|
||||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]],
|
||||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]],
|
||||
module_id: str | None = None,
|
||||
) -> ir.Function:
|
||||
"""生成 Win32 控制台 Ctrl+C 处理器:按累计耗时降序写入每函数计时文件。
|
||||
"""生成 Win32 控制台 Ctrl+C 处理器:按**自身耗时**降序写入每函数计时文件。
|
||||
|
||||
落盘格式(追加):
|
||||
# time <module_sha1>\\n
|
||||
<func_name> <acc_ticks> <enter_count>\\n
|
||||
module_id 为空时省略头部 sha1 行(留空,不伪造)。
|
||||
<func_name> <self_tick> <inc_tick> <enter_count>\\n
|
||||
module_id 为空时省略头部 sha1 行(留空,不伪造)。列含义(单位均为 tick):
|
||||
- self_tick:自身耗时(exclusive,见 _EmitHotspotTimeCounters),已扣除本
|
||||
函数内**被插桩**子调用的 inclusive 之和;但**未插桩**的被调方(间接调用、
|
||||
无定义体的外部函数如 strcmp/memcpy、深度溢出跳过的调用)仍计入其中。
|
||||
- inc_tick:包含式耗时(inclusive,含全部被调方)。
|
||||
- enter_count:被进入次数(含递归/重入),用于交叉校验。
|
||||
|
||||
- 排序:与调用点计数同法,落盘前用选择排序按累计 tick 降序输出,只输出
|
||||
进入次数 >0 的函数;tick 相同时按编号 N 升序(稳定、可复现)。
|
||||
- acc_ticks 单位为 @llvm.readcyclecounter() 的 tick(x86_64 上即 CPU 周期);
|
||||
enter_count 为函数被进入次数(含递归/重入),用于交叉校验。
|
||||
- 排序:与调用点计数同法,落盘前用选择排序按 self_tick 降序输出,只输出
|
||||
进入次数 >0 的函数;self_tick 相同时按编号 N 升序(稳定、可复现)。
|
||||
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT
|
||||
fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认
|
||||
处理继续,使 Ctrl+C 仍能终止程序。
|
||||
@@ -1363,39 +1472,48 @@ class BaseGenMixin:
|
||||
mode_ptr: ir.Constant = self._create_string_global(
|
||||
'a', name=self._UniqueGlobalName('__fhs_ftime_mode'))
|
||||
fmt_ptr: ir.Constant = self._create_string_global(
|
||||
'%s %llu %llu\n', name=self._UniqueGlobalName('__fhs_ftime_fmt'))
|
||||
'%s %llu %llu %llu\n', name=self._UniqueGlobalName('__fhs_ftime_fmt'))
|
||||
fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_ftime_fp')
|
||||
if module_id:
|
||||
hdr_fmt_ptr: ir.Constant = self._create_string_global(
|
||||
'# time %s\n', name=self._UniqueGlobalName('__fhs_ftime_hdr_fmt'))
|
||||
'# time %s name self inc enter (tick; self excludes instrumented callees)\n',
|
||||
name=self._UniqueGlobalName('__fhs_ftime_hdr_fmt'))
|
||||
mid_ptr: ir.Constant = self._create_string_global(
|
||||
module_id, name=self._UniqueGlobalName('__fhs_ftime_module_id'))
|
||||
b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_ftime_write_hdr')
|
||||
# 归属元数据全局数组:运行期按索引取 "函数名 / 累计耗时地址 / 进入次数地址"
|
||||
# 归属元数据全局数组:运行期按索引取 "函数名 / 自身耗时地址 / 包含式地址 / 进入次数地址"
|
||||
name_ty: ir.ArrayType = ir.ArrayType(i8p, n)
|
||||
sumptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||||
selfptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||||
incptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||||
enterptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||||
name_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, name_ty, name=self._UniqueGlobalName('__fhs_ftime_name'))
|
||||
name_gv.linkage = 'internal'
|
||||
name_gv.global_constant = True
|
||||
name_gv.initializer = ir.Constant(name_ty, [
|
||||
self._create_string_global(rec[2], name=self._UniqueGlobalName(f'__fhs_ftime_name_{k}'))
|
||||
self._create_string_global(rec[3], name=self._UniqueGlobalName(f'__fhs_ftime_name_{k}'))
|
||||
for k, rec in enumerate(records)
|
||||
])
|
||||
sum_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, sumptr_ty, name=self._UniqueGlobalName('__fhs_ftime_sum_ptrs'))
|
||||
sum_gv.linkage = 'internal'
|
||||
sum_gv.global_constant = True
|
||||
sum_gv.initializer = ir.Constant(sumptr_ty, [
|
||||
self_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, selfptr_ty, name=self._UniqueGlobalName('__fhs_ftime_self_ptrs'))
|
||||
self_gv.linkage = 'internal'
|
||||
self_gv.global_constant = True
|
||||
self_gv.initializer = ir.Constant(selfptr_ty, [
|
||||
ir.Constant(i64p, rec[0].get_reference()) for rec in records
|
||||
])
|
||||
inc_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, incptr_ty, name=self._UniqueGlobalName('__fhs_ftime_inc_ptrs'))
|
||||
inc_gv.linkage = 'internal'
|
||||
inc_gv.global_constant = True
|
||||
inc_gv.initializer = ir.Constant(incptr_ty, [
|
||||
ir.Constant(i64p, rec[1].get_reference()) for rec in records
|
||||
])
|
||||
enter_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, enterptr_ty, name=self._UniqueGlobalName('__fhs_ftime_enter_ptrs'))
|
||||
enter_gv.linkage = 'internal'
|
||||
enter_gv.global_constant = True
|
||||
enter_gv.initializer = ir.Constant(enterptr_ty, [
|
||||
ir.Constant(i64p, rec[1].get_reference()) for rec in records
|
||||
ir.Constant(i64p, rec[2].get_reference()) for rec in records
|
||||
])
|
||||
# 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈
|
||||
used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_ftime_used')
|
||||
@@ -1406,7 +1524,7 @@ class BaseGenMixin:
|
||||
bestv_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_bestv')
|
||||
i_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_i')
|
||||
b.store(c0_64, k_ptr)
|
||||
# 选择排序:外层 k 走 0..n-1,每轮在未输出项里选累计耗时最大者(相同取 N 小者)
|
||||
# 选择排序:外层 k 走 0..n-1,每轮在未输出项里选自身耗时(self)最大者(相同取 N 小者)
|
||||
loop_k: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_k')
|
||||
body_k: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_k_body')
|
||||
loop_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i')
|
||||
@@ -1432,10 +1550,10 @@ class BaseGenMixin:
|
||||
b.position_at_end(body_i)
|
||||
used_i: ir.Value = b.load(b.gep(used, [c0_32, i_val]))
|
||||
b.cbranch(b.icmp_signed('==', used_i, c0_8), scan_i, next_i)
|
||||
# scan_i: v = *sum_ptrs[i];同时把进入次数非 0 作为"有样本"判据
|
||||
# scan_i: v = *self_ptrs[i](自身耗时);同时把进入次数非 0 作为"有样本"判据
|
||||
b.position_at_end(scan_i)
|
||||
sum_p: ir.Value = b.load(b.gep(sum_gv, [c0_32, i_val]))
|
||||
v_val: ir.Value = b.load(sum_p)
|
||||
self_p: ir.Value = b.load(b.gep(self_gv, [c0_32, i_val]))
|
||||
v_val: ir.Value = b.load(self_p)
|
||||
enter_p: ir.Value = b.load(b.gep(enter_gv, [c0_32, i_val]))
|
||||
entered: ir.Value = b.load(enter_p)
|
||||
has: ir.Value = b.icmp_signed('>', entered, c0_64)
|
||||
@@ -1456,9 +1574,10 @@ class BaseGenMixin:
|
||||
idx: ir.Value = b.load(best_ptr)
|
||||
b.store(c1_8, b.gep(used, [c0_32, idx]))
|
||||
name_ptr: ir.Value = b.load(b.gep(name_gv, [c0_32, idx]))
|
||||
sp: ir.Value = b.load(b.gep(sum_gv, [c0_32, idx]))
|
||||
sp: ir.Value = b.load(b.gep(self_gv, [c0_32, idx]))
|
||||
ip: ir.Value = b.load(b.gep(inc_gv, [c0_32, idx]))
|
||||
ep: ir.Value = b.load(b.gep(enter_gv, [c0_32, idx]))
|
||||
b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(sp), b.load(ep)],
|
||||
b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(sp), b.load(ip), b.load(ep)],
|
||||
name='fhs_ftime_write')
|
||||
b.store(b.add(b.load(k_ptr), c1_64), k_ptr)
|
||||
b.branch(loop_k)
|
||||
|
||||
Reference in new issue
Block a user