原作者:江凌

Cpu-Profiling 概览

V8默认每毫秒采样(可配置),目前可生成Call-Tree,Tick Rank,Flame Chart, 如图:

线程驱动

V8内建的 Profiler 由2个线程驱动,线程名称1:V8:ProfEventProc, 线程2: V8:Profiler, 如下图:(这里以linux平台为讨论参考)

  • 线程1,主要是发送信号到主线程,处理采样结果,加入到Call-Tree;
  • 线程2,主要负责采样数据的持久化。
  • 线程1,2分离,主要是避免持久化的IO过程对采样分析线程的影响。
  • Tick Event 由1MB缓存的循环队列ticks_buffer_ 维护采样集合。
  • Code Event 由无锁队列[1] events_buffer_维护内建事件采样。

信号处理

void SignalHandler::HandleProfilerSignal(int signal, siginfo_t* info,void* context) {USE(info);if (signal != SIGPROF) return;Isolate* isolate = Isolate::UnsafeCurrent();if (isolate == NULL || !isolate->IsInUse()) {// We require a fully initialized and entered isolate.return;}if (v8::Locker::IsActive() &&!isolate->thread_manager()->IsLockedByCurrentThread()) {return;}....sampler->SampleStack(state); //记录函数栈

信号处理函数异常处理包括:1)SIGPROF 过滤;2)isolate 是否完全初始化校验;3)自锁的情况判断。
处理完上述情况后,就是提取 context 并记录。

函数栈帧

void ProfileGenerator::RecordTickSample(const TickSample& sample) {// Allocate space for stack frames + pc + function + vm-state.ScopedVector<CodeEntry*> entries(sample.frames_count + 3);
}

这里记录函数栈,pc 指针等。

下面我们一起来看下IA32平台的函数栈帧获取的原理。

可以发现,每调用一次函数,都会对调用者的栈基址(ebp)进行压栈操作,并且由于栈基址是由当时栈顶指针(esp)而来,会发现,各层函数的栈基址很巧妙的构成了一个链,即当前的栈基址指向下一层函数栈基址所在的位置,如下图所示:

了解了函数的调用过程,想要回溯调用栈也就很简单了,首先获取当前函数的栈基址(寄存器ebp)的值,然后获取该地址所指向的栈的值,该值也就是下层函数的栈基址,找到下层函数的栈基址后,重复刚才的动作,即可以将每一层函数的栈基址都找出来,这也就是我们所需要的调用栈了。

V8 对函数栈帧做了一层封装,并细化了各种帧,StackFrame、EntryFrame、ExitFrame、StandardFrame 等等,详见frame.cc, 这里暂不做分析。

节点关系

-- 'CodeMap' { CodeTree tree_; int next_shared_id_; }
-- 'CodeEntry' {LogEventsAndTags tag_ ; Name builtin_id_ ; int shared_id_; ...}
-- 'ProfileNode' {ProfileTree* tree_; CodeEntry* entry_; unsigned self_ticks_; HashMap children_; List<ProfileNode*> children_list_; unsigned id_;}
-- 'ProfileTree' { CodeEntry root_entry_; unsigned next_node_id_; ProfileNode* root_;}
-- 'CpuProfile' { List<ProfileNode*> samples_; ProfileTree top_down_; Time start_time_; Time end_time_;}
-- 'ProfileGenerator' {CodeMap code_map_; CpuProfilesCollection* profiles_; CodeEntry* program_entry_; ...}

CodeTree由一颗伸展树[3]组织起来, 而相对应的内部一一对应的ProfileNode由HashMap映射,ProfileTree自身有链表组织串联。

                     | root_  |/      \     \           \| child1 | child2 | ... | childn |/        |       |   \|child1|...|childn|  |child1|...|childn|  ....

后序遍历实现分析

class Position {public:explicit Position(ProfileNode* node): node(node), child_idx_(0) { }INLINE(ProfileNode* current_child()) {return node->children()->at(child_idx_);}INLINE(bool has_current_child()) {return child_idx_ < node->children()->length();}INLINE(void next_child()) { ++child_idx_; }ProfileNode* node; // 子节点, 它的子节点用List<ProfileNode*> children_list_;存储private:int child_idx_; // List的迭代器
};
// Non-recursive implementation of a depth-first post-order tree traversal.
// 非递归版本的深度后序遍历实现
template <typename Callback>
void ProfileTree::TraverseDepthFirst(Callback* callback) {List<Position> stack(10); // 初始大小为10stack.Add(Position(root_)); // 加入根节点while (stack.length() > 0) { Position& current = stack.last(); // 取出栈顶元素 | root_(底) | root_left |root_left_left | ....| 顶|if (current.has_current_child()) { // 存在子节点callback->BeforeTraversingChild(current.node, current.current_child());stack.Add(Position(current.current_child())); // 加入子节点,直到全部加入} else {callback->AfterAllChildrenTraversed(current.node); // callback, delete nodeif (stack.length() > 1) {Position& parent = stack[stack.length() - 2]; // 取出父节点callback->AfterChildTraversed(parent.node, current.node); parent.next_child(); // 注意:parent是引用,会改变child_idx_的值}// Remove child from the stack.stack.RemoveLast(); // 移出栈顶}}
}

值得注意的是:由于数据结构的组织,无法实现中序遍历。

  Binary Tree :0/   \1     4/   \2     3* step0 : stack | #0 |
* step1 : stack | #0 | #1 | #2 |  // Add node
* step2 : stack | #0 | #1 |       // Traversed node#2 -->|@2
* step3 : stack | #0 | #1 | #3 |  // Add right node#3
* step4 : stack | #0 | #1 |       // Traversed node#3 -->|@3
* step5 : stack | #0 |            // Traversed node#1 -->|@1
* step6 : stack | #0 | #4         // Add right node#4
* step7 : stack | #0 |            // Traversed node#1 -->|@4
* step8 : stack |                 // Traversed node#1 -->|@0
总的来说: 2->3->1->4->0, 实现了后续遍历。

参考

  • [1]http://coolshell.cn/articles/8239.html 无锁队列
  • [2]http://www.spongeliu.com/165.html Linux内核信号
  • [3]http://zh.wikipedia.org/wiki/%E4%BC%B8%E5%B1%95%E6%A0%91 伸展树

V8 Profiler 揭秘相关推荐

  1. 深入理解浏览器原理和架构|硬核

    本文用47张图带你了解「浏览器的发展史」.「浏览器的架构」.「浏览器的基本原理」以及 「浏览器的其它小知识」 ???? 正文开始 浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示HTML文档. ...

  2. 【浏览器】1022- 47 张图带你走进浏览器的世界!

    大家好,我是零一,每个开发者(尤其是前端工程师)或多或少会跟浏览器打交道,那么你们有没有想过去深入了解浏览器呢?无论是因为好奇还是为了面试,相信你们一定在网上搜过不少关于浏览器相关的知识和文章,或者也 ...

  3. 怎样对流媒体进行压力测试_对node工程进行压力测试与性能分析「干货」

    作者:小黎 转发链接:https://mp.weixin.qq.com/s/WBe7ZLoqFD9UqNusnv_IDA 前言 在系统上线前,为了看下系统能承受多大的并发和并发下的负载情况,常常会先进 ...

  4. 介绍适用于Visual Studio的node.js工具

    Just when you thought it couldn't be crazier in Redmond, today they are introducing node.js Tools fo ...

  5. 对node工程进行压力测试与性能分析

    在系统上线前,为了看下系统能承受多大的并发和并发下的负载情况,进行了一轮压测.在压测过程中,发现服务器的cpu飚的的非常高,而tps,接口耗时.服务可用等都是正常的,卧槽,这就奇了怪了,自己想了半天也 ...

  6. Rokid ALL in ONE 全栈开发套件开箱——强人工智能入门指南[1]

    强 AI 很弱,弱 AI 很强. --薛定饿(作者的猫,图见文末) 首先,感谢 SF 和 Rokid 提供的 试用机会. SF的小伙伴们,大家好,欢迎收看这一期的 Integ's Hack Show ...

  7. 揭秘:支付宝小程序 V8 Worker 技术演进

    简介: 本文分享支付宝小程序 V8 Worker 相关工作沉淀和总结,包括技术演进.基础架构.基础功能.以及 JS 引擎能力输出,以及一些优化方案等.欢迎同学们共同探讨,指正.(文末福利:<小程 ...

  8. 揭秘鸿蒙系统中的 JS 开发框架

    作者 | justjavac 来源 | justjavac 今天鸿蒙终于发布了,开发者们也终于"沸腾"了. 源码托管在国内知名开源平台码云上,https://gitee.com/o ...

  9. JavaScript工作原理(二):V8引擎和5招高效代码

    本系列的第一篇文章重点介绍了引擎,运行时和调用栈的概述.第二篇文章将深入V8的JavaScript引擎的内部.我们还会提供一些关于如何编写更好的JavaScript代码的技巧. 概述 JavaScri ...

最新文章

  1. java 控制 android_Java For Android - 流程控制
  2. Nginx重要结构request_t解析之http请求的获取
  3. 二:Go编程语言规范-类型
  4. 【HDU - 6574】Rng(概率,古典概型)
  5. 《跟我一起写Makefile》读书笔记(2)
  6. Android分辨率比例为1,修改android5.1系统分辨率
  7. 配置IIS支持PHP
  8. HSmartWindowControl 之 显示图像
  9. 【JZOJ4922】【NOIP2017提高组模拟12.17】环
  10. and desgin Vue页面,使用watch监视, <a-input>内的值, 自动计算出填入框的值,用vue中watch监听input组件的变化
  11. 智能交通 路侧智能感知 应用层数据格式
  12. ICO文件缩略图显示错误
  13. 科达视频系统设置服务器,科达KDV-VS视频录像点播系统用户手册.doc
  14. Win11 22H2怎么退回之前版本?Win11回滚Win10系统教程(三种方法)
  15. android 2.2(froyo)源码下载
  16. 1031. 两个非重叠子数组的最大和-构造子数组和数组遍历数组
  17. 从最近的比赛学习CTR/CVR
  18. 【实用工具】Chrome浏览器英文翻译插件推荐:Google翻译
  19. CC00006.CloudOpenStack——|OpenStack组件.V01|——|openstack-keystone|组件说明|
  20. 使用Python Link SDK接入阿里云物联网平台

热门文章

  1. MySQL第3天:MySQL的架构介绍之linux版安装
  2. pthread_cleanup_push与pthread_cleanup_pop的目的、作用
  3. UltraEdit使用注册机激活详解
  4. jvm六:主动使用(1.new一个对象, 2.反射)
  5. UI开发模式-容器模式
  6. 第九课 特殊权限set_uid、stick_bit,软链接,硬链接
  7. Linux知识点记录
  8. Table 'xxxxx' is marked as crashed and last 解决办法
  9. NuGet学习笔记(2) 使用图形化界面打包自己的类库
  10. 临时表 DML 产生redo 问题说明