간단한 Mark-Sweep Garbage Collector를 Zig로 포팅

16964 단어 zigctoolingalgorithms
저는 최근에 my first Zig project을 완료했습니다. Bob Nystrom의 간단한 마크 스윕 가비지 컬렉터(모던 클래식!)를 C에서 Zig로 포팅한 것입니다.

Zig은 비교적 새로운 언어이므로 경험에 대한 개요를 제공하고 도구에 대해 이야기하는 것이 도움이 될 것이라고 생각했습니다.

이 게시물은 프로그램의 몇 가지 부분을 설명하지만 읽기를 적극 권장하는 Bob's original article을 정의할 수 없습니다. 그의 C 구현은 프로젝트 저장소에 포함되어 있으며 사용 가능합니다on GitHub.

어쨌든, 우리는 간다 ...

지그 설치



설치 과정은 매우 간단합니다. Releases page에서 최신 버전을 다운로드하고 폴더에 압축을 풀고 경로에 추가하십시오.

예를 들어 이 프로젝트의 지속적인 통합을 위해 Zig가 설치되는 방법은 다음과 같습니다.

wget https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz
tar xf zig-linux*.tar.xz
echo "`pwd`/zig-linux-x86_64-0.9.1" >> $GITHUB_PATH


실제 설치는 파일을 더 적절한 위치에 배치하지만 CI에는 잘 작동합니다.
zig는 대형 정적으로 연결된 실행 파일로 제공됩니다. 종속성이 필요하지 않습니다 👍.

빔 플러그인



즉시 사용할 수 있는 Vim은 Zig의 편집자로서 충분히 잘 작동하지만 경험은 다소 실망스럽습니다. 그래서 Vim configuration for Zig 플러그인 get 구문 강조를 설치했습니다.

예상치 못한 보너스:

This plugin enables automatic code formatting on save by default using zig fmt.



Go와 마찬가지로 매우 편리하여 코드 형식에 대해 걱정할 필요가 없습니다. 그 외에도zig fmt 기본 구문 오류도 포착합니다.



나는 코딩할 때 자주 저장하는 경향이 있으므로 이것은 코드 상태에 대한 지속적인 피드백을 제공합니다.

인쇄



방금 디버그 인쇄를 사용하여 출력을 작성했습니다.

const print = @import("std").debug.print;
print("Collected {} objects, {} remaining.\n", .{ numObjects - self.numObjects, self.numObjects });


흥미로운 관찰은 C varargs 대신 Zig가 anonymous struct을 사용한다는 것입니다.

While 루프, If's, 옵션 등



Zig는 표준 C 구문에 비해 많은 개선 사항을 제공합니다.

루프의 일부로 증가:

while (i < self.stack_size) : (i += 1) {


개봉기optionals:

while (object.*) |obj| {
if (object.data.pair.head) |head| {


핸들error unions :

var a = vm.pushPair() catch unreachable;


C에 대한 기타 기타 개선 사항에는 표준 명명 규칙(호출할 수 없는 변수의 이름에 밑줄 표시) 및 보다 깔끔한 부울 유형이 포함됩니다.

또한 함수를 struct 유형의 일부로 정의하여 보다 자연스러운 코드 구성을 허용하는 것도 유용합니다. 이것은 오랫동안 대부분의 언어에서 표준 기능이었습니다. Including C++ .

포인터


sweep 함수는 포인터 대 포인터를 사용하여 모든 개체의 연결된 목록을 탐색하고 사용되지 않는 개체의 연결을 해제합니다.

void sweep(VM* vm)
{
  Object** object = &vm->firstObject;
  while (*object) {
    if (!(*object)->marked) {
      /* This object wasn't reached, so remove it from the list and free it. */
      Object* unreached = *object;

      *object = unreached->next;
      free(unreached);

      vm->numObjects--;
    } else {
      /* This object was reached, so unmark it (for the next GC) and move on to
       the next. */
      (*object)->marked = 0;
      object = &(*object)->next;
    }
  }
}


아마도 놀랍게도 이 함수는 새로운 구문으로 Zig에서 라인별로 표현할 수 있습니다.

fn sweep(self: *VM) void {
    var object = &(self.first_object);
    while (object.*) |obj| {
        if (!obj.marked) {
            // This object wasn't reached, so remove it from the list and free it.
            var unreached = obj;

            object.* = obj.next;
            self.allocator.destroy(unreached);

            self.num_objects -= 1;
        } else {
            // This object was reached, so unmark it (for the next GC) and move on to
            // the next.
            obj.marked = false;
            object = &(obj.next);
        }
    }
}


테스트



모든 테스트는 test declarations으로 포팅되었습니다. 이들은 zig test mark-sweep.zig를 통해 호출할 수 있습니다.

test "test 1" {
    const allocator = std.testing.allocator;
    print("Test 1: Objects on stack are preserved.\n", .{});

    var _vm = try VM.init(allocator);
    var vm = &_vm;
    try vm.pushInt(1);
    try vm.pushInt(2);

    vm.gc();

    try std.testing.expect(vm.num_objects == 2);
    vm.deinit();
}


또 다른 중요한 포인트! Zig에서는 메모리 할당을 위해 malloc를 호출하지 않습니다. 대신 많은 할당자 중 하나를 사용하여 당면한 상황에 가장 적합한 방식으로 메모리를 할당합니다. 우리의 코드는 할당자가 VM 인스턴스 수명 동안 메모리 할당을 위해 전달되고 유지될 수 있는 일반적인 라이브러리처럼 구성됩니다.

테스트를 위해 메모리가 누수되는 경우 테스트 케이스에 실패하는 testing.allocator를 사용합니다. 이것은 vm.deinit()root objects을 모두 제거하고 수집을 수행하므로 테스트를 향상시킵니다. 코드가 제대로 작동하면 할당된 모든 메모리가 해제됩니다.

gdb로 디버깅



C를 광범위하게 디버깅한 후gdb Zig 코드 디버깅에 적합하다는 사실에 놀랐습니다.

justin@ubuntu:~/Documents/zig-mark-sweep-gc$ make
zig build-exe mark-sweep.zig
justin@ubuntu:~/Documents/zig-mark-sweep-gc$ ./mark-sweep 
Performance Test.
Collected 20000 objects, 0 remaining.
justin@ubuntu:~/Documents/zig-mark-sweep-gc$ gdb mark-sweep 
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from mark-sweep...
(gdb) break VM.sweep
Breakpoint 1 at 0x23091c: file /home/justin/Documents/zig-mark-sweep-gc/mark-sweep.zig, line 91.
(gdb) run
Starting program: /home/justin/Documents/zig-mark-sweep-gc/mark-sweep 
Performance Test.

Breakpoint 1, VM.sweep (self=0x7fffffffd938) at /home/justin/Documents/zig-mark-sweep-gc/mark-sweep.zig:91
91              var object = &(self.first_object);
(gdb) n
92              while (object.*) |obj| {
(gdb) 


결론



지금은 그게 다입니다. 전체적으로 이것은 Zig에 대한 훌륭한 첫 경험이었고 가까운 장래에 더 많은 Zig를 작성하기를 기대합니다.

유용한 링크:
  • Zig Language Reference
  • How to read the standard library source code
  • Zig Standard Library Documentation
  • Zig Tutorial
  • 좋은 웹페이지 즐겨찾기