前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Clang插件统计方法耗时

Clang插件统计方法耗时

作者头像
Helloted
发布2022-06-08 10:30:02
8770
发布2022-06-08 10:30:02
举报
文章被收录于专栏:Helloted
0、统计函数耗时原理

LLVM的优化和转换工作就需要通过PASS来进行,就像下面这种图,PASS就像流水线上的操作工一样对中间代码IR进行优化,每个PASS完成特定的优化工作。

所有的pass都是llvm的Pass类的子类,通过重写继承的虚函数来实现特定的功能。根据pass不同的功能分类,继承的类也不同,比如:ModulePass , CallGraphSCCPass, FunctionPass , LoopPass, RegionPass, BasicBlockPass,llvm系统会根据实例的类别来判断pass的功能,然后将其整合到现有的优化体系中去。

FunctionPASS会遍历我们编译的每个函数,在遍历编译的函数过程中,在函数运行之前获取当前时间,在函数运行之后获取当前时间,二者相减,可以得到函数的运行时间。

1、整体过程

待插入函数

代码语言:javascript
复制
#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则是在函数的最末尾插入,用于记录当前时间并与之前函数开始记录的时间做差值,把函数名称和耗时打印出来。

代码语言:javascript
复制
    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、函数开始
代码语言:javascript
复制
    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、函数结束
代码语言:javascript
复制
        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、运行效果
代码语言:javascript
复制
-[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:

  1. 复制栈帧,debug时候(或crash时候),可以看到调用堆栈。
  2. 保存寄存器。
  3. 调用hook_objc_msgSend_before (保存lr和记录函数调用开始时间)
  4. 恢复寄存器。
  5. 调用objc_msgSend
  6. 保存寄存器。
  7. 调用hook_objc_msgSend_after (返回lr和函数结束时间减去开始时间,得到函数耗时)
  8. 恢复寄存器。
  9. ret。 参考TimeProfiler
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、统计函数耗时原理
  • 1、整体过程
  • 2、函数开始
  • 3、函数结束
  • 4、运行效果
  • 5、统计方法耗时的其他方案
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档