0、统计函数耗时原理
LLVM的优化和转换工作就需要通过PASS来进行,就像下面这种图,PASS就像流水线上的操作工一样对中间代码IR进行优化,每个PASS完成特定的优化工作。
所有的pass都是llvm的Pass类的子类,通过重写继承的虚函数来实现特定的功能。根据pass不同的功能分类,继承的类也不同,比如:ModulePass , CallGraphSCCPass, FunctionPass , LoopPass, RegionPass, BasicBlockPass,llvm系统会根据实例的类别来判断pass的功能,然后将其整合到现有的优化体系中去。
FunctionPASS会遍历我们编译的每个函数,在遍历编译的函数过程中,在函数运行之前获取当前时间,在函数运行之后获取当前时间,二者相减,可以得到函数的运行时间。
1、整体过程
待插入函数
#include <stdio.h>
#include <sys/time.h>
long my_fun_b(){
struct timeval star;
gettimeofday(&star, NULL);
long b = star.tv_sec * 1000000 + star.tv_usec;
return b;
}
void my_fun_e(char *name, long b){
struct timeval end;
gettimeofday(&end, NULL);
long e = end.tv_sec * 1000000 + end.tv_usec;
long t = e - b;
printf("%s %ld us\n",name, t);
}
my_fun_b是函数的最开始,插入并用于记录当前时间;
my_fun_e则是在函数的最末尾插入,用于记录当前时间并与之前函数开始记录的时间做差值,把函数名称和耗时打印出来。
bool runOnFunction(Function &F) override
{
// 1. 已经插入的不需要再次插入
if (F.getName().startswith("my_fun_"))
return false;
// 2. 记录开始时间
Value* beginTime = nullptr;
if (!insert_begin_inst(F, beginTime))
return false;
// 3. 方法结束时统计方法耗时,开始的时间记录作为参数
insert_return_inst(F, beginTime);
return false;
}
2、函数开始
bool insert_begin_inst(Function &F, Value*& beginTime)
{
// 0.函数最开始的BasicBlock
LLVMContext &context = F.getParent()->getContext();
BasicBlock &bb = F.getEntryBlock();
// 1. 获取要插入的函数
FunctionCallee beginFun = F.getParent()->getOrInsertFunction("my_fun_b",FunctionType::get(Type::getInt64Ty(context), {}, false));
// 2. 构造函数
// Value *beginTime = nullptr;
CallInst *inst = nullptr;
IRBuilder<> builder(context);
inst = builder.CreateCall(beginFun);
if (!inst) {
llvm::errs() << "Create First CallInst Failed\n";
return false;
}
// 3. 获取函数开始的第一条指令
Instruction *beginInst = dyn_cast<Instruction>(bb.begin());
// 4. 将inst插入
inst->insertBefore(beginInst);
// 5.根据返回值记录开始时间
beginTime = inst;
return true;
}
3、函数结束
void insert_return_inst(Function &F, Value* beginTime)
{
LLVMContext &context = F.getParent()->getContext();
for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I)
{
// 函数结尾的BasicBlock
BasicBlock &BB = *I;
for (BasicBlock::iterator I = BB.begin(), E = BB.end(); I != E; ++I)
{
ReturnInst *IST = dyn_cast<ReturnInst>(I);
if (!IST)
continue;
// end_func 类型
FunctionType *endFuncType = FunctionType::get(
Type::getVoidTy(context),
{Type::getInt8PtrTy(context),Type::getInt64Ty(context)},
false
);
// end_func
FunctionCallee endFunc = BB.getModule()->getOrInsertFunction("my_fun_e", endFuncType);
// 构造end_func
IRBuilder<> builder(&BB);
IRBuilder<> callBuilder(context);
CallInst* endCI = callBuilder.CreateCall(endFunc,
{
builder.CreateGlobalStringPtr(BB.getParent()->getName()),
beginTime
}
);
// 插入end_func(struction)
endCI->insertBefore(IST);
}
}
}
4、运行效果
-[AppDelegate application:didFinishLaunchingWithOptions:] 5 us
-[AppDelegate application:configurationForConnectingSceneSession:options:] 63 us
-[SceneDelegate window] 0 us
-[SceneDelegate setWindow:] 0 us
-[SceneDelegate window] 0 us
-[SceneDelegate window] 0 us
-[SceneDelegate scene:willConnectToSession:options:] 0 us
-[SceneDelegate window] 0 us
-[SceneDelegate window] 0 us
-[SceneDelegate window] 0 us
-[ViewController viewDidLoad] 0 us
-[SceneDelegate sceneWillEnterForeground:] 0 us
-[SceneDelegate sceneDidBecomeActive:] 0 us
-[SceneDelegate window] 0 us
-[SceneDelegate window] 0 us
5、统计方法耗时的其他方案
可以通过hook objc_msgSend:
-
复制栈帧,debug时候(或crash时候),可以看到调用堆栈。
-
保存寄存器。
-
调用hook_objc_msgSend_before (保存lr和记录函数调用开始时间)
-
恢复寄存器。
-
调用objc_msgSend
-
保存寄存器。
-
调用hook_objc_msgSend_after (返回lr和函数结束时间减去开始时间,得到函数耗时)
-
恢复寄存器。
-
ret。