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的使用方法。
class Rope final : public cppgc::GarbageCollected<Rope> {
};
Rope的成员变量如果继承自GCed,必须用 Member模板
class Rope final : public cppgc::GarbageCollected<Rope> {
private:
std::string part_;
cppgc::Member<Rope> next_;
};
必须要实现Trace方法,指出该对象会引用哪些对象
public:
explicit Rope(std::string part, Rope* next = nullptr)
: part_(part), next_(next) {}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(next_); }
创建对象
auto* greeting = cppgc::MakeGarbageCollected<Rope>(
heap->GetAllocationHandle(), "Hello ",
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::Run
或 JobTask::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]: