V8 Profiler 揭秘
原作者:江凌
Cpu-Profiling 概覽
V8默認(rèn)每毫秒采樣(可配置),目前可生成Call-Tree,Tick Rank,Flame Chart, 如圖:
線程驅(qū)動(dòng)
V8內(nèi)建的 Profiler 由2個(gè)線程驅(qū)動(dòng),線程名稱1:V8:ProfEventProc, 線程2: V8:Profiler, 如下圖:(這里以linux平臺(tái)為討論參考)
- 線程1,主要是發(fā)送信號(hào)到主線程,處理采樣結(jié)果,加入到Call-Tree;
- 線程2,主要負(fù)責(zé)采樣數(shù)據(jù)的持久化。
- 線程1,2分離,主要是避免持久化的IO過程對(duì)采樣分析線程的影響。
- Tick Event 由1MB緩存的循環(huán)隊(duì)列ticks_buffer_ 維護(hù)采樣集合。
- Code Event 由無(wú)鎖隊(duì)列[1] events_buffer_維護(hù)內(nèi)建事件采樣。
信號(hào)處理
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); //記錄函數(shù)棧信號(hào)處理函數(shù)異常處理包括:1)SIGPROF 過濾;2)isolate 是否完全初始化校驗(yàn);3)自鎖的情況判斷。
處理完上述情況后,就是提取 context 并記錄。
函數(shù)棧幀
void ProfileGenerator::RecordTickSample(const TickSample& sample) {// Allocate space for stack frames + pc + function + vm-state.ScopedVector<CodeEntry*> entries(sample.frames_count + 3); }這里記錄函數(shù)棧,pc 指針等。
下面我們一起來看下IA32平臺(tái)的函數(shù)棧幀獲取的原理。
可以發(fā)現(xiàn),每調(diào)用一次函數(shù),都會(huì)對(duì)調(diào)用者的棧基址(ebp)進(jìn)行壓棧操作,并且由于?;肥怯僧?dāng)時(shí)棧頂指針(esp)而來,會(huì)發(fā)現(xiàn),各層函數(shù)的?;泛芮擅畹臉?gòu)成了一個(gè)鏈,即當(dāng)前的棧基址指向下一層函數(shù)?;匪诘奈恢?#xff0c;如下圖所示:
了解了函數(shù)的調(diào)用過程,想要回溯調(diào)用棧也就很簡(jiǎn)單了,首先獲取當(dāng)前函數(shù)的棧基址(寄存器ebp)的值,然后獲取該地址所指向的棧的值,該值也就是下層函數(shù)的?;?#xff0c;找到下層函數(shù)的?;泛?#xff0c;重復(fù)剛才的動(dòng)作,即可以將每一層函數(shù)的?;范颊页鰜?#xff0c;這也就是我們所需要的調(diào)用棧了。
V8 對(duì)函數(shù)棧幀做了一層封裝,并細(xì)化了各種幀,StackFrame、EntryFrame、ExitFrame、StandardFrame 等等,詳見frame.cc, 這里暫不做分析。
節(jié)點(diǎn)關(guān)系
-- '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]組織起來, 而相對(duì)應(yīng)的內(nèi)部一一對(duì)應(yīng)的ProfileNode由HashMap映射,ProfileTree自身有鏈表組織串聯(lián)。
| root_ |/ \ \ \| child1 | child2 | ... | childn |/ | | \|child1|...|childn| |child1|...|childn| ....后序遍歷實(shí)現(xiàn)分析
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; // 子節(jié)點(diǎn), 它的子節(jié)點(diǎn)用List<ProfileNode*> children_list_;存儲(chǔ)private:int child_idx_; // List的迭代器 }; // Non-recursive implementation of a depth-first post-order tree traversal. // 非遞歸版本的深度后序遍歷實(shí)現(xiàn) template <typename Callback> void ProfileTree::TraverseDepthFirst(Callback* callback) {List<Position> stack(10); // 初始大小為10stack.Add(Position(root_)); // 加入根節(jié)點(diǎn)while (stack.length() > 0) { Position& current = stack.last(); // 取出棧頂元素 | root_(底) | root_left |root_left_left | ....| 頂|if (current.has_current_child()) { // 存在子節(jié)點(diǎn)callback->BeforeTraversingChild(current.node, current.current_child());stack.Add(Position(current.current_child())); // 加入子節(jié)點(diǎn),直到全部加入} else {callback->AfterAllChildrenTraversed(current.node); // callback, delete nodeif (stack.length() > 1) {Position& parent = stack[stack.length() - 2]; // 取出父節(jié)點(diǎn)callback->AfterChildTraversed(parent.node, current.node); parent.next_child(); // 注意:parent是引用,會(huì)改變child_idx_的值}// Remove child from the stack.stack.RemoveLast(); // 移出棧頂}} }值得注意的是:由于數(shù)據(jù)結(jié)構(gòu)的組織,無(wú)法實(shí)現(xiàn)中序遍歷。
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, 實(shí)現(xiàn)了后續(xù)遍歷。參考
- [1]http://coolshell.cn/articles/8239.html 無(wú)鎖隊(duì)列
- [2]http://www.spongeliu.com/165.html Linux內(nèi)核信號(hào)
- [3]http://zh.wikipedia.org/wiki/%E4%BC%B8%E5%B1%95%E6%A0%91 伸展樹
總結(jié)
以上是生活随笔為你收集整理的V8 Profiler 揭秘的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不再任人欺负!手游安全的进阶之路
- 下一篇: Git005--工作区和暂存区