Skip to content

Commit

Permalink
feat: more escapable types && fix docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Chronostasys committed Jun 14, 2024
1 parent 2b0ac64 commit b19b928
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 58 deletions.
28 changes: 2 additions & 26 deletions book/src/systemlib/gc.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
# GC

> !WIP:此页面描述的功能仍然在实验性阶段,可能有些细节没有实现完毕。

pivot-lang 是一门使用gc进行内存管理的语言。

pivot-lang 的gc目前是使用rust写的,采用一种叫做`immix`[\[1\]]的mark region算法。

在以前的版本中,我们使用的是我们现在叫做`simple gc`的垃圾回收算法,他是一个简单的mark-sweep算法。
它由于性能问题和不支持多线程等原因最终被`immix`取代。不过我们仍然保留了它的代码,并且设置了一个编译开关(_feature:simple_gc_),可以在编译时手动选择使用该gc算法。
它由于性能问题和不支持多线程等原因最终被`immix`取代。


[\[1\]]: https://www.cs.utexas.edu/users/speedway/DaCapo/papers/immix-pldi-2008.pdf

## Simple GC

simple gc就和他的名字一样,[代码](https://github.com/Pivot-Studio/pivot-lang/blob/master/vm/src/gc/_simple_gc.rs)十分简单(算法实现大概100行)。它没有自己的allocator,直接
使用C中的`malloc``free`进行内存分配和释放。

simple gc是一个[保守gc算法(Conservative Garbage Collection)](https://www.cs.cornell.edu/courses/cs6120/2020fa/blog/modern-gc/),它没有能力精确的分辨出哪些内存是指针,哪些不是。因此它会试图将所有的内存都当做指针来处理。

目前simple gc不建议在生产环境中使用,其代码保留下来是为了方便我们在未来的版本中进行性能对比,以及为未来用于教育目的做准备。


## Immix GC

immix gc是一种mark region算法,它是一个精确的gc算法。但是请注意,它的精确建立在使用它
的项目提供的特殊支持之上。可以认为目前我们的immix gc实现 __是为pivot-lang量身定制__ 的。pl编译器为了和我们的immix gc配合,会在编译时专门生成一些额外的代码,如果缺少这些代码,immix gc将无法正常工作。
所以虽然理论上我们可以将我们的immix gc用到其他项目中,这么做的效益很可能并不是很高--
缺少编译器的支持,使用者将需要手动添加那些额外的代码。

immix gc的实现代码在[这里](https://github.com/Pivot-Studio/pivot-lang/blob/master/immix)。它是天生支持多线程使用的,但是我们的pivot-lang目前还不支持多线程。


## Benchmark

我们对两种gc算法进行了一些基准测试,事实证明immix gc的回收性能要比simple gc __快近20倍__。如果你对这些测试感兴趣,可以在项目根目录运行`make bench`查看immix的benchmark,或者运行`make bench-simple-gc`查看simple gc的benchmark。

下方分别是simple gc和immix gc的benchmark结果,测试于2023年1月,commit 62b5c52c01e8133f5300e33a0131a50ba0c8d0de

![](2023-01-24-23-23-55.png)

![](2023-01-24-23-25-06.png)
immix gc的实现代码在[这里](https://github.com/Chronostasys/immix)
38 changes: 21 additions & 17 deletions book/src/systemlib/immix.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,32 @@
此页面仍在编写中,内容可能有疏漏
```


## Table of Contents

<!-- toc -->

## Overview

此gc是我们基于[immix gc论文](https://www.cs.utexas.edu/users/speedway/DaCapo/papers/immix-pldi-2008.pdf)实现的,
大部分的实现细节都与论文一致,对于一些论文没提到的细节我们自行进行了实现,参考了很多别的gc项目。该gc是一个支持多线程使用的、
基于shadow stack的,精确mark-region 非并发(Concurrency) 并行(Parallelism) gc。

此gc是我们基于[immix GC论文](https://www.cs.utexas.edu/users/speedway/DaCapo/papers/immix-pldi-2008.pdf)实现的,
大部分的实现细节都与论文一致,对于一些论文没提到的细节我们自行进行了实现,参考了很多别的GC项目。该GC是一个支持多线程使用的、
基于StackMap的,半精确mark-region 非并发(Concurrency) 并行(Parallelism) gc。

```admonish tip title="gc的并发(Concurrency)与并行(Parallelism)"
gc中并发和并行是两个不同的术语,并发gc指的是能够在应用不暂停的基础上进行回收的gc,
而并行gc指的是gc在回收的时候能够使用多个线程同时进行工作。一个gc可以既是并行的也是并发的,
我们的immix gc目前只具备并行能力
```

这里有一些创建该gc过程中我们主要参考的资料,列表如下:

- [immix gc论文](https://www.cs.utexas.edu/users/speedway/DaCapo/papers/immix-pldi-2008.pdf)
- [playxe 的 immixcons(immix gc的一个rust实现,回收存在bug)-- 很多底层内存相关代码是参考该gc完成的,还有在函数头中加入自定义遍历函数的做法](https://github.com/playXE/libimmixcons)
- [给scala-native使用的一个immix gc的C实现](https://github.com/scala-native/immix)
- [康奈尔大学CS6120课程关于immix gc的博客,可以帮助快速理解论文的基本思路](https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/immix/)


## General Description

本gc是为pl __定制的__,虽然理论上能被其他项目使用,但是对外部项目的支持**并不是主要目标**
本gc是为pl __定制的__,虽然理论上能被其他项目使用,但是对外部项目的支持 __并不是主要目标__

pl的组件包含一个全局的`GlobalAllocator`,然后每个`mutator`线程会包含一个独属于该线程的`Collector`,每个`Collector`中包含一个
`ThreadLocalAllocator`。在线程使用gc相关功能的时候,该线程对应的`Collector`会自动被创建,直到线程结束或者
Expand Down Expand Up @@ -114,7 +112,6 @@ graph LR;
Immix GC由于分配以line为单位,在内存利用率上稍有不足,但是其算法保证了极其优秀的内存局部性,配合TLA、GA的设计也大大
减少了线程间的竞争。


## Allocation

### Global Allocator
Expand All @@ -130,7 +127,8 @@ GA同时维护一个`free`动态数组,用于存储已经被回收的block,
```admonish info
潜在的优化点:使用bitmap来记录block的使用情况,这样可以减少动态数组的开销
```
```admonish warning

```admonish warning
如果GA在分配新block的时候,发现`current`指针已经超出了初始申请的内存空间,会导致程序panic。这个行为应该在未来被改善
```

Expand All @@ -149,6 +147,7 @@ TLA是线程本地的,每个线程都会有一个TLA,负责分配line,和
所有完全被占用的block会被加入到`unavailable`数组中,不会被重复利用。

TLA的小对象分配策略如下:

<center>

```mermaid
Expand Down Expand Up @@ -183,16 +182,18 @@ graph TD;

## Mark

Mark阶段的主要工作是标记所有被使用的line和block,以便在后续的sweep阶段进行回收。我们的mark算法是**精确**
Mark阶段的主要工作是标记所有被使用的line和block,以便在后续的sweep阶段进行回收。我们的mark算法是__精确__
这点对evacuation算法的实现至关重要。

精确GC有两个要求:

- root的精确定位
- 对象的精确遍历

我们的精确root定位是基于**Stack Map**,这部分细节过于复杂,将在[单独的文档](stackmap.md)中介绍。
我们的精确root定位是基于__Stack Map__,这部分细节过于复杂,将在[单独的文档](stackmap.md)中介绍。

对象的精确遍历是通过编译器支持实现的,plimmix将所有heap对象分类为以下4种:

- Atomic Object:原子对象,不包含指针的对象,如整数、浮点数、字符串等
- Pointer Object:指针对象,该对象本身是一个指针
- Complex Object:复杂对象,该对象可能包含指针
Expand All @@ -204,11 +205,13 @@ Mark阶段的主要工作是标记所有被使用的line和block,以便在后

对于Complex Object,编译器需要在对象开始位置增加一个`vtable`字段,该字段的值指向该类型的遍历函数。此遍历函数由编译器生成,
其签名为:

```rust,ignore
pub type VisitFunc = unsafe fn(&Collector, *mut u8);
// vtable的签名,第一个函数是mark_ptr,第二个函数是mark_complex,第三个函数是mark_trait
pub type VtableFunc = fn(*mut u8, &Collector, VisitFunc, VisitFunc, VisitFunc);
```

在标记的时候,我们会调用对象的vtable对他进行遍历

对于Trait Object,我们需要遍历他指向实际值的指针
Expand Down Expand Up @@ -253,13 +256,16 @@ graph LR;
```

对于`ComplexObject1`,他的vtable函数逻辑如下:

```rust,ignore
fn vtable_complex_obj1(&self, mark_ptr: VisitFunc, mark_complex: VisitFunc, mark_trait: VisitFunc){
mark_ptr(self.PointerField)
mark_complex(self.ComplexField)
}
```

而对于`ComplexField`,他的vtable函数逻辑如下:

```rust,ignore
fn vtable_complex_field(&self, mark_ptr: VisitFunc, mark_complex: VisitFunc, mark_trait: VisitFunc){
mark_ptr(self.PointerField)
Expand All @@ -279,10 +285,10 @@ mark queue为空,则标记过程结束。
尽管这的确可以看作是一个递归的过程,但是此过程一定不能使用递归的方式实现,因为递归的方式在复杂程序中可能会导致栈溢出。
```


## Sweep

Sweep阶段的主要工作是:

- 回收所有未被标记的block
- 修正所有line的header
- 计算evacuation需要的一些信息
Expand All @@ -303,6 +309,7 @@ Sweep阶段的主要工作是:
## Evacuation

每次回收开始之前,我们会先判断是否需要进行反碎片化,目前的策略是只要recycle block>1就进行反碎片化。

```admonish info
优化点:如果处于内存用尽的紧急情况也应当进行evacuation,且threshold应当设置的更低
```
Expand Down Expand Up @@ -377,7 +384,6 @@ graph TD;

每次驱逐是以分配的对象为单位,如果一个block被标记为待evacuate,那么在驱逐过程中,该block中的所有对象都一定会被驱逐。


请注意,一部分其他gc的驱逐算法中的自愈需要读写屏障的参与,immix不需要。这带来了较大的mutator性能提升。

```admonish warning
Expand Down Expand Up @@ -415,13 +421,11 @@ fn add_sub_ndoe(root: *mut Node) {
这导致gc无法在回收过程中对其进行修正,就会进一步导致`root.next`指向的地址不正确,从而导致程序出错。
```


```admonish tip title="多线程情况下驱逐算法的安全性"
在多线程情况下,是存在两个线程同时驱逐一个对象的可能的,在这种情况下一些同步操作必不可少,但是并不需要加锁。
我们通过一个cas操作来保证只有一个线程能够成功驱逐该对象。
```


## 性能

我们与bdwgc进行了很多比较,数据证明在大多数情况下,我们的分配算法略慢于bdwgc,与malloc速度相当,但是在回收的时候,我们的回收速度要快于bdwgc。
Expand All @@ -433,9 +437,9 @@ fn add_sub_ndoe(root: *mut Node) {

你可以从[这里](https://github.com/Chronostasys/bdwgcvsimmix-bench)下载测试代码,在你的机器上运行并进行比较。这里我提供一组笔者机器上的测试数据截图

![](immix.png)
![immix](immix.png)

![](bdw.png)
![bdw](bdw.png)

测试环境为MacBook Pro (16-inch, 2021) Apple M1 Pro 16 GB,可以看出immix在此环境中已经具有近4倍的性能优势。

Expand Down
18 changes: 5 additions & 13 deletions book/src/systemlib/stackmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ llvm提供了一系列的gc相关api,首先是gc策略,我们可以给每个
两个C文件一个定义了我们的GC策略,一个定义了我们的stackmap格式和生成方式。

我们的stackmap格式如下:
```

```Rust
Header {
i64 : Stack Map Version (current version is 1)
i32 : function 数量
Expand Down Expand Up @@ -117,22 +118,13 @@ immix gc提供了`gc_init`函数,该函数接受一个stackmap指针,会加

### 基于stackmap的精确root定位实现

为了遍历函数调用栈,我们使用`backtrace.rs`包,该包封装了一些平台相关的函数调用栈遍历的实现。
```admonish warning
栈爬取的实现不同平台差异巨大,很容易出现bug。目前我们发现在mac aarch64上如果使用lld进行链接会导致该backtrace包出现
segment fault,这个问题目前使用ld替代lld进行规避。
```

遍历的时候通过`backtrace.rs`拿到当前ip寄存器的值,然后去我们构建的stackmap中查找到当前函数栈的root进行遍历,遍历完成后继续向上层函数栈遍历。
注意这里遍历的起点不在mutator代码中,而在gc代码中,所以遍历的开头和结尾查不到对应记录是完全正常的。
为了遍历函数调用栈,在老版本中我们使用`backtrace.rs`包,该包封装了一些平台相关的函数调用栈遍历的实现。

```admonish
潜在优化点:其实从gc的函数到目标语言最底层函数的调用栈层数在运行时是固定的,所以这里其实可以优化,跳过前几个栈帧,直接从目标语言最底层函数开始遍历。
```
在新版本中,由于`libbacktrace`中使用了全居锁,性能不好,我们决定手动进行栈遍历。为了完成这一点,我们需要
在mutator调用可能触发GC的runtime函数时,传入当前的`sp`寄存器值,之后利用StackMap中记录的各个函数栈的大小来一步一步往上爬取。

## 参考资料

1. [llvm stackmap 文档](https://llvm.org/docs/StackMaps.html)
2. [llvm gc 文档](https://llvm.org/docs/GarbageCollection.html)
3. [读取llvm默认生成的stackmap例子](https://github.com/KavinduZoysa/test-GCs)

2 changes: 1 addition & 1 deletion immix
2 changes: 1 addition & 1 deletion src/ast/builder/llvmbuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ impl<'a, 'ctx> LLVMBuilder<'a, 'ctx> {
&format!("heapptr_{}", name),
)
.unwrap();
if tp.is_atomic() || !matches!(tp, PLType::Arr(_)) {
if !matches!(tp, PLType::Arr(_)) {
let pos = match declare {
Some(Pos {
line: 0,
Expand Down

0 comments on commit b19b928

Please sign in to comment.