Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >V8 Heap Profiler 的实现

V8 Heap Profiler 的实现

作者头像
theanarkh
发布于 2022-05-16 07:57:22
发布于 2022-05-16 07:57:22
51800
代码可运行
举报
文章被收录于专栏:原创分享原创分享
运行总次数:0
代码可运行

前言:V8 Heap Profiler 用于收集哪些代码分析了多少内存的信息。本文介绍 V8 中关于这部分的实现,代码来自 V8 10.2。

入口函数是 StartSamplingHeapProfiler。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  bool StartSamplingHeapProfiler(uint64_t sample_interval, int stack_depth, v8::HeapProfiler::SamplingFlags);

主要的参数是 sample_interval。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool HeapProfiler::StartSamplingHeapProfiler(
    uint64_t sample_interval, int stack_depth,
    v8::HeapProfiler::SamplingFlags flags) {
  if (sampling_heap_profiler_.get()) {
    return false;
  }
  sampling_heap_profiler_.reset(new SamplingHeapProfiler(
      heap(), names_.get(), sample_interval, stack_depth, flags));
  return true;
}

主要逻辑是创建一个 SamplingHeapProfiler 对象。来看一下这个对象的构造函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SamplingHeapProfiler::SamplingHeapProfiler(
    Heap* heap, StringsStorage* names, uint64_t rate, int stack_depth,
    v8::HeapProfiler::SamplingFlags flags)
    : isolate_(Isolate::FromHeap(heap)),
      heap_(heap),
      allocation_observer_(heap_, static_cast<intptr_t>(rate), rate, this,
                           isolate_->random_number_generator()),
      names_(names),
      profile_root_(nullptr, "(root)", v8::UnboundScript::kNoScriptId, 0,
                    next_node_id()),
      stack_depth_(stack_depth),
      rate_(rate),
      flags_(flags) {

  heap_->AddAllocationObserversToAllSpaces(&allocation_observer_,
                                           &allocation_observer_);
}

代码比较简单,主要是初始化一些字段,其中最重要的是 allocation_observer_ 字段,该字段是一个 Observer 对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Observer : public AllocationObserver {
   public:
    Observer(Heap* heap, intptr_t step_size, uint64_t rate,
             SamplingHeapProfiler* profiler,
             base::RandomNumberGenerator* random)
        : AllocationObserver(step_size),
          profiler_(profiler),
          heap_(heap),
          random_(random),
          rate_(rate) {}

   protected:
    void Step(int bytes_allocated, Address soon_object, size_t size) override {
      if (soon_object) {
        profiler_->SampleObject(soon_object, size);
      }
    }

    intptr_t GetNextStepSize() override { return GetNextSampleInterval(rate_); }

   private:
    intptr_t GetNextSampleInterval(uint64_t rate);
    SamplingHeapProfiler* const profiler_;
    Heap* const heap_;
    base::RandomNumberGenerator* const random_;
    uint64_t const rate_;
  };

Observer 继承 AllocationObserver,AllocationObserver 是一个可以监听堆对象分配的接口。其中最重要的是 Step 方法,该方法在 V8 分配 n 个字节时被回调。创建完 Observer 对象后,V8 会把该对象通过 AddAllocationObserversToAllSpaces 注册到堆中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Heap::AddAllocationObserversToAllSpaces(
    AllocationObserver* observer, AllocationObserver* new_space_observer) {
  // 遍历各种堆,注册观察者
  for (SpaceIterator it(this); it.HasNext();) {
    Space* space = it.Next();
    if (space == new_space()) {
      space->AddAllocationObserver(new_space_observer);
    } else {
      space->AddAllocationObserver(observer);
    }
  }
}

AddAllocationObserversToAllSpaces 往新生代、老生代等堆内存管理对象中注册观察者,来看一下 AddAllocationObserver。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Space::AddAllocationObserver(AllocationObserver* observer) {
  allocation_counter_.AddAllocationObserver(observer);
}

void AllocationCounter::AddAllocationObserver(AllocationObserver* observer) {
  intptr_t step_size = observer->GetNextStepSize();
  size_t observer_next_counter = current_counter_ + step_size;
  /*
      struct AllocationObserverCounter final {
        AllocationObserverCounter(AllocationObserver* observer, size_t prev_counter, size_t next_counter)
            : observer_(observer),
              prev_counter_(prev_counter),
              next_counter_(next_counter) {}

        AllocationObserver* observer_;
        size_t prev_counter_;
        size_t next_counter_;
      };
  */
  intptr_t step_size = observer->GetNextStepSize();
  size_t observer_next_counter = current_counter_ + step_size;
  observers_.push_back(AllocationObserverCounter(observer, current_counter_, observer_next_counter));
}

这样就完成了观察者的注册,接着看调用观察者的逻辑。具体在分配内存时会调用 InvokeAllocationObservers,比如新生代的 AllocateRawUnaligned 函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// soon_object:分配的地址,object_size 分配的大小
void AllocationCounter::InvokeAllocationObservers(Address soon_object,
                                                  size_t object_size,
                                                  size_t aligned_object_size) {
  bool step_run = false;
  step_in_progress_ = true;
  size_t step_size = 0;
  // 遍历观察者
  for (AllocationObserverCounter& aoc : observers_) {
    if (aoc.next_counter_ - current_counter_ <= aligned_object_size) {
      {
        DisallowGarbageCollection no_gc;
        aoc.observer_->Step(
            static_cast<int>(current_counter_ - aoc.prev_counter_), soon_object,
            object_size);
      }
    }
  }
}

接着看之前注册的观察者的 Step 函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Step(int bytes_allocated, Address soon_object, size_t size) override {
    if (soon_object) {
      profiler_->SampleObject(soon_object, size);
    }
}

void SamplingHeapProfiler::SampleObject(Address soon_object, size_t size) {
  DisallowGarbageCollection no_gc;
  HandleScope scope(isolate_);
  HeapObject heap_object = HeapObject::FromAddress(soon_object);
  Handle<Object> obj(heap_object, isolate_);

  Local<v8::Value> loc = v8::Utils::ToLocal(obj);
  // 处理栈信息
  AllocationNode* node = AddStack();
  node->allocations_[size]++;
  // 记录信息
  auto sample = std::make_unique<Sample>(size, node, loc, this, next_sample_id());
  samples_.emplace(sample.get(), std::move(sample));
}

SampleObject 首先处理了当前调用栈,这样才知道是谁申请了该内存。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SamplingHeapProfiler::AllocationNode* SamplingHeapProfiler::AddStack() {
  AllocationNode* node = &profile_root_;
  std::vector<SharedFunctionInfo> stack;
  JavaScriptFrameIterator frame_it(isolate_);
  int frames_captured = 0;
  bool found_arguments_marker_frames = false;
  // 还有栈且没有达到需要捕获的栈深度
  while (!frame_it.done() && frames_captured < stack_depth_) {
    JavaScriptFrame* frame = frame_it.frame();
    // 记录栈
    if (frame->unchecked_function().IsJSFunction()) {
      SharedFunctionInfo shared = frame->function().shared();
      stack.push_back(shared);
      frames_captured++;
    } else {
      found_arguments_marker_frames = true;
    }
    frame_it.Advance();
  }
  // 遍历栈,并找到对应的代码信息
  for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
    SharedFunctionInfo shared = *it;
    const char* name = this->names()->GetCopy(shared.DebugNameCStr().get());
    int script_id = v8::UnboundScript::kNoScriptId;
    if (shared.script().IsScript()) {
      Script script = Script::cast(shared.script());
      script_id = script.id();
    }
    // 构造树(层次)结构
    node = FindOrAddChildNode(node, name, script_id, shared.StartPosition());
  }

  return node;
}

AddStack 捕获了当前的栈信息并且构造了一个相应的树结构。紧接着可以调用 GetAllocationProfile 获取收集到的信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
v8::AllocationProfile* SamplingHeapProfiler::GetAllocationProfile() {
  std::map<int, Handle<Script>> scripts;
  {
    Script::Iterator iterator(isolate_);
    for (Script script = iterator.Next(); !script.is_null();
         script = iterator.Next()) {
      scripts[script.id()] = handle(script, isolate_);
    }
  }
  auto profile = new v8::internal::AllocationProfile();
  TranslateAllocationNode(profile, &profile_root_, scripts);
  profile->samples_ = BuildSamples();

  return profile;
}

GetAllocationProfile 对收集到的数据进行处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
v8::AllocationProfile::Node* SamplingHeapProfiler::TranslateAllocationNode(
    AllocationProfile* profile, SamplingHeapProfiler::AllocationNode* node,
    const std::map<int, Handle<Script>>& scripts) {

  node->pinned_ = true;
  Local<v8::String> script_name =
      ToApiHandle<v8::String>(isolate_->factory()->InternalizeUtf8String(""));
  int line = v8::AllocationProfile::kNoLineNumberInfo;
  int column = v8::AllocationProfile::kNoColumnNumberInfo;
  std::vector<v8::AllocationProfile::Allocation> allocations;
  allocations.reserve(node->allocations_.size());
  if (node->script_id_ != v8::UnboundScript::kNoScriptId) {
    auto script_iterator = scripts.find(node->script_id_);
    if (script_iterator != scripts.end()) {
      Handle<Script> script = script_iterator->second;
      if (script->name().IsName()) {
        Name name = Name::cast(script->name());
        script_name = ToApiHandle<v8::String>(
            isolate_->factory()->InternalizeUtf8String(names_->GetName(name)));
      }
      line = 1 + Script::GetLineNumber(script, node->script_position_);
      column = 1 + Script::GetColumnNumber(script, node->script_position_);
    }
  }
  for (auto alloc : node->allocations_) {
    allocations.push_back(ScaleSample(alloc.first, alloc.second));
  }

  profile->nodes_.push_back(v8::AllocationProfile::Node{
      ToApiHandle<v8::String>(
          isolate_->factory()->InternalizeUtf8String(node->name_)),
      script_name, node->script_id_, node->script_position_, line, column,
      node->id_, std::vector<v8::AllocationProfile::Node*>(), allocations});
  v8::AllocationProfile::Node* current = &profile->nodes_.back();
  for (const auto& it : node->children_) {
    // 递归处理
    current->children.push_back(
        TranslateAllocationNode(profile, it.second.get(), scripts));
  }
  node->pinned_ = false;
  return current;
}

然后构造 sample。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const std::vector<v8::AllocationProfile::Sample>
SamplingHeapProfiler::BuildSamples() const {
  std::vector<v8::AllocationProfile::Sample> samples;
  samples.reserve(samples_.size());
  for (const auto& it : samples_) {
    const Sample* sample = it.second.get();
    samples.emplace_back(v8::AllocationProfile::Sample{
        sample->owner->id_, sample->size, ScaleSample(sample->size, 1).count,
        sample->sample_id});
  }
  return samples;
}

细节比较多,大致流程已经分析完毕,最终就拿到了 Heap Profile 的数据。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
V8 新生代垃圾回收的实现
前言:因为最近在做一些 gc track 的事情,所以打算了解一下 V8 GC 的实现。介绍 V8 GC 的文章网上已经有很多,就不打算再重复介绍。本文主要介绍一下新生代 GC 的实现,代码参考 V8 10.2,因为 GC 的实现非常复杂,只能介绍一些大致的实现,读者需要对 V8 GC 有一定的了解,比如新生代是分为 from 和 to 两个 space,然后在 GC 时是如何处理的。
theanarkh
2022/05/16
7820
V8 CPU Profiler 的实现
前言:CPU Profiler 是应用性能诊断和优化的利器,本文介绍 V8 中关于这部分的实现,细节比较多也比较复杂,大致分析一下原理,代码来自 V8 10.2。
theanarkh
2022/05/16
8680
V8 CPU Profiler 的实现
内存泄漏的定位与排查:Heap Profiling 原理解析
系统长时间运行之后,可用内存越来越少,甚至导致了某些服务失败,这就是典型的内存泄漏问题。这类问题通常难以预测,也很难通过静态代码梳理的方式定位。Heap Profiling 就是帮助我们解决此类问题的。
PingCAP
2021/11/18
1.8K0
V8 GC 的实现
前言:GC 是一个古老、复杂并且很 Cool 的技术,本文大概介绍一下早期 V8 中关于 GC 实现的部分,代码版本 0.1.5,早期版本利于快速理解整体的逻辑,因为现代版本已经非常复杂。
theanarkh
2022/12/06
3780
V8 GC 的实现
探索v8源码:事件循环 Microtasks (微任务)
点击上方蓝字,发现更多精彩 导语 Microtasks(微任务)是事件循环中一类优先级比较高的任务,本文通过一个有趣的例子探索其运行时机。从两年前被动接受知识 "当浏览器JS引擎调用栈弹空的时候,才会执行 Microtasks 队列",到两年后主动深入探索源码后了解到的 "当 V8 执行完调用要返回 Blink 时,由于 MicrotasksScope 作用域失效,在其析构函数中检查 JS 调用栈是否为空,如果为空就会运行 Microtasks。"。同时文章中介绍了用于探索浏览器运行原理的一些工具。 一个
腾讯VTeam技术团队
2021/04/19
1.8K0
javascript & c++ - v8pp 实现解析
v8 和 node.js 的流行让 js/ts 相关的脚本开发也慢慢走入像游戏业务开发这些领域, 本文主要从 v8pp 的实现出发, 让读者熟悉极大提高 v8 易用性, 提供诸如像c++类导出到javascript等功能的 v8pp 的同时, 也对怎么在c++ 中嵌入式的使用 v8 虚拟机有个基础的了解. 依赖v8本身完备的实现和提供的基础对象, c++ & v8 的跨语言中间件的实现复杂度大幅度下降, 除了因为 js 本身使用 prototype 设计带来的一定程度的理解成本和机制转换成本外, 其他部分都会比像 python 等的跨语言中间件来得简单, 从代码量上来说, v8pp 的代码量也远少于笔者之前剖析过的 pybind11. 从某种层面来说, 基于 v8 的跨语言中间件, v8本身提供的机制解决了绝大部分问题, 剩下的一小部分问题, 是需要 v8pp 本身来解决的.
fangfang
2023/10/16
9840
javascript & c++ - v8pp 实现解析
通过快照加速 Node.js 的启动
前言:随着 Node.js 的越来越强大,代码量也变得越来越多,不可避免地拖慢了 Node.js 的启动速度,针对这个问题,Node.js 社区通过 V8 的 snapshot 技术对 Node.js 的启动做了优化,在 github 有很多关于此的 issue 讨论,大家有兴趣也可以去看一下。通过快照加速启动是一个非常复杂的过程,这需要对 V8 有深入的理解。本文介绍一下如何在 Node.js 中使用快照加速 Node.js 的启动。以 v16.13.1 为例,社区一直在优化这里面的速度,不同的版本的速度可能不一样。
theanarkh
2022/05/16
1.5K0
通过快照加速 Node.js 的启动
V8 global.gc() 的实现
前言:在 Node.js 中我们有时候会使用 global.gc() 主动触发 gc 来测试一些代码,因为我们知道 V8 gc 的执行时机是不定的。但是可能很少同学知道 global.gc() 的实现,本文介绍一些在 V8 中关于这部分的实现。
theanarkh
2022/07/01
5140
V8 堆外内存 ArrayBuffer 垃圾回收的实现
前言:V8 除了我们经常讲到的新生代和老生代的常规堆内存外,还有另一种堆内存,就是堆外内存。堆外内存本质上也是堆内存,只不过不是由 V8 进行分配,而是由 V8 的调用方分配,比如 Node.js,但是是由 V8 负责 GC 的。本文介绍堆外内存的一种类型 ArrayBuffer 的 GC 实现。
theanarkh
2022/05/16
1K0
V8 堆外内存 ArrayBuffer 垃圾回收的实现
通过v8 0.1.5源码分析js的编译、执行过程
我们主要关注Compile和Run这两个函数。这两个函数都属于Script这个类,我们看看定义。
theanarkh
2020/02/18
2.4K0
Inside V8:平平无奇mksnapshot
mksnapshot是v8编译过程中的一个中间产物,看名字平平无奇,也甚少文章着重介绍它,但实际上它并不是它名字表述那样只是生成个快照,而是内藏玄机:
车雄生
2023/09/01
8450
如何实现一个 APM watchdog
Hello,大家好,之前说不打算更新公众号了,后面有时间的话还是会偶尔更新下,记录和分享下一些技术相关的内容,今天分享下如何实现一个 APM watchdog。
theanarkh
2023/10/30
2790
如何实现一个 APM watchdog
how2heap 系列记录
对https://github.com/shellphish/how2heap上的例子进行讲解,记录调试过程,方便日后快速回忆利用技巧。
De4dCr0w
2019/10/16
1.5K0
how2heap 系列记录
Google V8引擎的CVE-2018-17463漏洞分析
该漏洞是由于对JSCreateObject操作的side-effect判断存在错误,导致优化过程中可消除类型检查节点,从而造成类型混淆,最终可执行任意代码
迅达集团
2019/06/26
2K1
Google V8引擎的CVE-2018-17463漏洞分析
pprof 的原理与实现
go 内置的 pprof API 在 runtime/pprof 包内, 它提供给了用户与 runtime 交互的能力, 让我们能够在应用运行的过程中分析当前应用的各项指标来辅助进行性能优化以及问题排查, 当然也可以直接加载 _ "net/http/pprof" 包使用内置的 http 接口 来进行使用, net 模块内的 pprof 即为 go 替我们封装好的一系列调用 runtime/pprof 的方法, 当然也可以自己直接使用
梦醒人间
2021/05/11
2.7K0
nodejs之启动源码解析浅析
int main(int argc, char *argv[]) { #if defined(__linux__) char** envp = environ; while (*envp++ != nullptr) {} Elf_auxv_t* auxv = reinterpret_cast<Elf_auxv_t*>(envp); for (; auxv->a_type != AT_NULL; auxv++) { if (auxv->a_type == AT_SECURE) {
theanarkh
2019/03/19
2.6K0
nodejs之启动源码解析浅析
编译和使用V8
V8编译是个比较麻烦的事情,不仅是下载、编译的过程,不同系统、不同编译器、不同C++版本都可能会出现不同的问题。之前编译的时候没有记录步骤,这次简单记录一下编译V8的过程,我的工作目录是/code/v8_code/。
theanarkh
2021/09/16
2.6K0
No.js 中 V8 堆外内存管理和字符编码解码的实现
前言:对于基于 V8 的 JS 运行时来说,堆外内存的管理是非常重要的一部分,因为 gc 的原因,V8 自己管理堆内存大小是有限制的,我们不能什么数据都往 V8 的堆里存储,比如我们想一下读取一个 1G 的文件,如果存到 V8 的堆,一下子就满了,所以我们需要定义堆外内存并进行管理。本文介绍 No.js 里目前支持的简单堆内存管理机制和字符编码解码的实现。
theanarkh
2021/10/11
1.3K0
深入理解 V8 Inspector
前言:本文介绍一下 V8 关于 Inspector 的实现,不过不会涉及到具体命令的实现,V8 Inspector 的命令非常多,了解了处理流程后,如果对某个命令感兴趣的话,可以单独去分析。
theanarkh
2021/11/04
1.2K0
使用V8和node轻松profile分析nodejs应用程序
我们使用nodejs写好了程序之后,要是想对该程序进行性能分析的话,就需要用到profile工具了。
程序那些事
2021/02/05
1K0
相关推荐
V8 新生代垃圾回收的实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验