V8 Profiler 揭秘
原作者:江凌
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 揭秘的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 不再任人欺负!手游安全的进阶之路
- 下一篇: Git005--工作区和暂存区