最直接方式是用functionpass在函数入口(首个basicblock起始)和所有出口(含returninst/resumeinst/unwindinst)插入gettimeofday计时逻辑,传入函数名字符串常量和时间戳,避免使用clock()等失真或线程不安全的api。

用 FunctionPass 插入 gettimeofday 计时代码
最直接的方式是在每个函数入口和出口插入时间采集逻辑,靠 FunctionPass 遍历所有函数,跳过已注入的辅助函数(如 my_fun_b、my_fun_e),避免重复插桩。关键不是“加计时”,而是确保插入点正确:入口必须在第一个 BasicBlock 的起始位置(用 F.getEntryBlock()),出口必须覆盖所有 return 指令(包括隐式 return 和异常 unwind 路径)。
常见错误是只处理 ReturnInst,漏掉 ResumeInst 或 UnwindInst,导致程序崩溃或计时不全。建议统一用 IRBuilder 在每个 terminator 前插入 my_fun_e 调用,并传入函数名字符串常量和开始时间值。
- 函数名需转为全局常量字符串,避免 runtime 动态构造开销
- 时间戳用
gettimeofday比clock()更准,但注意它返回的是微秒级整数,不是浮点秒 - 不要在
runOnModule里做耗时操作(比如遍历所有函数再统计),那会拖慢编译本身
为什么不能用 clock() 或 CLOCKS_PER_SEC
clock() 返回的是进程 CPU 时间,不是 wall-clock 时间,在多线程或系统调度干扰下严重失真;CLOCKS_PER_SEC 在不同平台定义不一(Linux 是 1000000,macOS 可能是 1000000000),且无法反映真实 I/O 或等待延迟。你真正想测的是“这个函数从进入执行到离开花了多少真实时间”,不是“它占用了多少 CPU 周期”。
更麻烦的是:clock() 在某些 libc 实现中是线程局部的,LLVM Pass 插入后若函数被多线程调用,计时器变量可能被多个线程竞争写入,结果不可信。而 gettimeofday 是 syscall,返回全局单调递增的时间戳,只要不跨进程共享同一计时变量,就安全得多。
- 务必用
struct timeval+gettimeofday,而不是std::chrono—— 后者依赖 C++ 运行时,LLVM IR 层不认 - 不要试图在 Pass 里调用
printf;输出必须交给运行时函数(如my_fun_e)在程序实际运行时完成 - 若目标平台无
gettimeofday(如裸机环境),得换用rdtsc指令,但要注意乱序执行和频率漂移问题
编译瓶颈定位:别只看函数耗时总和
函数级耗时数据只是表象。真正的编译瓶颈往往藏在 Pass 执行顺序、IR 大小膨胀、或某几个 Pass 的反复触发中。比如一个 LoopVectorizePass 在某个函数上卡住 8 秒,很可能是因为它在尝试展开一个有副作用的循环体,反复回退重试;而你在运行时看到的只是该函数耗时高,误以为是业务逻辑问题。
LLVM 提供了内置分析开关:-mllvm -print-after-all 会 dump 每个 Pass 前后的 IR,配合 diff 可快速识别哪次优化导致 IR 行数暴增;-time-passes 则直接输出各 Pass 自身耗时(单位 ms),精确到小数点后一位。
-
-time-passes输出里如果InstructionCombining占比超 40%,说明 IR 存在大量冗余指令,应检查前端是否生成了低效 IR - 若
GlobalDCE耗时异常高,大概率是模块里存在大量未使用的全局变量或弱符号,需清理 dead code - 启用
-debug-pass=Structure可打印 Pass 管线结构,确认你写的自定义 Pass 是否被多次执行(比如被放进AlwaysInliner循环里)
多线程场景下计时器失效怎么办
你写的计时器默认是全局变量累加,比如 TimeFor_foo += ...。一旦函数被多线程并发调用,这个写操作就是竞态的——没有原子性保证,最终结果必然小于真实总和,甚至出现负值(因寄存器重排或 cache 不一致)。
解决办法只有两个:要么彻底禁用多线程执行(加 -j1 编译),要么改用线程局部存储(TLS)。LLVM IR 支持 @llvm.thread.local.address 内建调用,但需要手写 TableGen 规则并链接 compiler-rt;更现实的做法是让运行时函数(如 my_fun_e)内部用 pthread key 或 __thread 变量暂存单线程耗时,最后由主线程统一合并。
- 别指望
std::atomic—— 它在 IR 层不存在,Clang 会降级为锁实现,反而引入更大开销 - 若只需粗略定位热点函数,可先忽略多线程问题,用单线程跑通逻辑再迭代
- 真正要上线的性能分析工具,必须把计时器变量声明为
thread_local并在 C++ 运行时初始化,否则 LLVM 无法生成正确 TLS 访问码
-time-passes 查编译阶段耗时。











