cppgc解决的问题是如何以垃圾回收的方式管理C++对象。
Chrome浏览器有两个重要组件,一个是Blink一个是v8。当用户创建了一个js对象时,这个对象由v8的gc管理,当用户创建了一个新DOM节点时,Blink管理这个对象。当用户用document.getElementById这类API时,一个js对象引用了一个DOM对象,如何跨引擎地管理内存就成了个问题。
现在CppGC基本是由V8实现,在Blink重度使用,使得V8一个js引擎和Blink一个C++编写的排版引擎的内存管理统一了起来。

用法

类要继承GarbageCollected模板,才能被管理,这个模板会禁用new 和 delete。
我们来创建一个字符串链表Rope类展示CppGC的使用方法。

  1. class Rope final : public cppgc::GarbageCollected<Rope> {
  2. };

Rope的成员变量如果继承自GCed,必须用 Member模板

  1. class Rope final : public cppgc::GarbageCollected<Rope> {
  2. private:
  3. std::string part_;
  4. cppgc::Member<Rope> next_;
  5. };

必须要实现Trace方法,指出该对象会引用哪些对象

  1. public:
  2. explicit Rope(std::string part, Rope* next = nullptr)
  3. : part_(part), next_(next) {}
  4. void Trace(cppgc::Visitor* visitor) const { visitor->Trace(next_); }

创建对象

  1. auto* greeting = cppgc::MakeGarbageCollected<Rope>(
  2. heap->GetAllocationHandle(), "Hello ",
  3. cppgc::MakeGarbageCollected<Rope>(heap->GetAllocationHandle(), "World!"));

注意要给MakeGarbageCollected传heap的handle,让它知道在哪个堆上申请对象
手动触发一次GC看看

heap->ForceGarbageCollectionSlow("CppGC example", "Testing");

对象因为保持栈扫描不会被回收。
最后优雅结束进程,回收所有对象。

cppgc::ShutdownProcess();

初始化

  // 创建一个默认的Platform,让cppgc::Heap可以执行任务以及申请内存
  auto cppgc_platform = std::make_shared<cppgc::DefaultPlatform>();
  // 初始化进程,必须在Heap::Create()之前初始化进程。
  cppgc::DefaultPlatform::InitializeProcess(cppgc_platform.get());
  // 创建一个由 CppGC 管理的堆
  std::unique_ptr<cppgc::Heap> heap = cppgc::Heap::Create(cppgc_platform);

依赖分析

CppGC要能正常工作,必须有一个实现了cppgc::Platform抽象类的对象。

class V8_EXPORT Platform {
 public:
  virtual ~Platform() = default;

  /**
   * 返回一个 Allocator 作为内存池。CppGC 在其中管理对象。
   */
  virtual PageAllocator* GetPageAllocator() = 0;

  /**
   * 一个单调计时器,精度起码是毫秒级别。推荐计时起始点不要早于世纪初。
   **/
  virtual double MonotonicallyIncreasingTime() = 0;

  /**
   * Foreground task runner that should be used by a Heap.
   */
  virtual std::shared_ptr<TaskRunner> GetForegroundTaskRunner() {
    return nullptr;
  }

  /**
   * 提交 job_task(JobTask对象) 让 Platform 并行执行。
   * 返回值是一个JobHandle对象,JobHandle对象关联一个Job,可以通过这个 JobHandle 
   * Join 或者 Cancel。
   */
  virtual std::unique_ptr<JobHandle> PostJob(
      TaskPriority priority, std::unique_ptr<JobTask> job_task) {
    return nullptr;
  }

  /**
   * 返回一个非空指针指向 TracingController。默认实现是一个空的 TracingController,仅
   * 消费追踪数据而没有副作用。
   */
  virtual TracingController* GetTracingController();
};

一个标准的JobTask实现。

class MyJobTask : public JobTask {
public:
    MyJobTask(...) : worker_queue_(...) {}
    // JobTask implementation.
    void Run(JobDelegate* delegate) override {
        while (!delegate->ShouldYield()) {
            // Smallest unit of work.
            auto work_item = worker_queue_.TakeWorkItem(); // Thread safe.
            if (!work_item) return;
            ProcessWork(work_item);
        }
    }
    size_t GetMaxConcurrency() const override {
        return worker_queue_.GetSize(); // Thread safe.
    }
};


auto handle = PostJob(TaskPriority::kUserVisible,
                      std::make_unique<MyJobTask>(...));
handle->Join();

一个简单的PostJob实现

std::unique_ptr<JobHandle> PostJob(
    TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
  return std::make_unique<DefaultJobHandle>(
            std::make_shared<DefaultJobState>(
                  this, std::move(job_task), kNumThreads));
}

如果一把锁可以被 JobTask::RunJobTask::GetMaxConcurrency 申请,那么持有此锁时禁止调用 PostJob,这会造成死锁。

构建

说了那么多,怎么使用 CppGC 作为一个库呢?
下载 depot_tools

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/path/to/depot_tools:$PATH

depot_tools 里有 Google 的 C++ 依赖下载工具 gclient
下载 v8 源码以及依赖

fetch v8
cd v8

v8提供了一个v8gen工具用来生成构建配置

tools/dev/v8gen.py x64.release.sample

配置编译选项

gn args out.gn/x64.release.sample

配置文件为

is_component_build = true
is_debug = false
target_cpu = "x64"
use_custom_libcxx = false
v8_monolithic = false
v8_use_external_startup_data = false
cppgc_is_standalone = true

这样就不会构建 v8 浪费时间。单独编译 CppGC。最终产物为 libcppgc.so, libv8_libbase.so, libv8_libplatform.so 以及可执行文件 cppgc_sample

参考文献

[1]: 高性能C++垃圾回收 v8博客
[2]: Oilpan: Blink的垃圾回收 YouTube
[3]: Oilpan 101: Basics, Common Pitfalls Google Slide
[4]: v8 源码在 github 的镜像 https://v8.dev/docs/source-code
[5]: depot tools的简介 https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up
[6]: 编译 V8 源码 https://zhuanlan.zhihu.com/p/25120909
[7]: