是否有可能为代表避免使用GC?

Mai*_*ein 3 d

是否有可能为代表避免使用GC?

我正在建立一个任务系统.我有一个带有本地任务队列的N-Threads.任务队列基本上只是一个Array!Fiber tasks.因为不鼓励将光纤发送到不同的线程,所以我向一个线程发送一个闭包/委托,从该委托创建光纤并将其放入数组中tasks.

现在我发送的代表是捕获变量的委托.

//Some Pseudo code

auto f = //some function;
auto cell = Cell(...);

auto del = () {
  let res = f();
  cell.write(res);
}

send(del);
Run Code Online (Sandbox Code Playgroud)

}

现在,单元格被堆分配并与原子计数器同步.然后,我可以检查原子计数器cell是否已经达到0,如果它已经,我可以安全地读取它.

问题是代理人捕获变量,在GC上分配变量.现在我只分配一个指针,这可能不是一个大问题,但我仍然想避免使用GC.

我该怎么做?

Ada*_*ppe 8

您可能已经知道这一切,但这是一个常见问题,所以我将写一些细节.

首先,让我们了解委托是什么.就像切片只是一个与长度配对的C数据指针一样,委托只是一个与函数指针配对的C数据指针.它们一起传递给期望它们的函数,就像定义它们一样

struct d_delegate {
    void* ptr; // yes, it is actually typed void*!
    T* funcptr; // this is actually a function pointer
};
Run Code Online (Sandbox Code Playgroud)

(注意,当你尝试在类方法中使用嵌套委托时,有一个数据ptr的原因是一些编译器错误背后的原因!)

void*就是指向数据并且与切片相似的内容,它可以来自各种各样的地方:

Object obj = new Object();
string delegate() dg = &obj.toString;
Run Code Online (Sandbox Code Playgroud)

在这一点上,dg.ptr指向obj,恰好是一个垃圾收集类对象,但只是因为我new在上面编辑它.

struct MyStruct {
    string doSomething() { return "hi"; }
}

MyStruct obj;

string delegate() dg = &obj.doSomething;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,obj由于我在上面的分配方式而生活在堆栈上,因此dg.ptr也指向该临时对象.

无论事情是委托或不说任何关于使用它的内存分配方案 - 这无疑是危险的,因为通过委托你可能会指向一个临时对象,你用它完成之前即会消失!(这就是为什么要使用GC的主要原因,以帮助防止这种免费使用后的错误.)

那么,如果代表可以来自任何对象,为什么他们被认为是GC那么多呢?好吧,当编译器认为委托的生命周期比外部函数长时,自动生成的闭包可以将局部变量复制到GC段.

void some_function(void delegate() dg);

void foo() {
    int a;
    void nested() {
        a++;
    }
    some_function(&nested);
}
Run Code Online (Sandbox Code Playgroud)

在这里,编译器会将变量复制a到GC段,因为它假设some_function将保留它的副本并希望防止使用后释放的错误(这是一个很难调试,因为它经常导致内存损坏!)以及内存泄漏.

但是,如果您通过scope在委托定义上使用关键字向编译器保证自己可以正确地执行此操作,那么它将信任您并将本地保留在原来的位置:

void some_function(scope void delegate() dg);
Run Code Online (Sandbox Code Playgroud)

保持其余部分相同,它将不再分配副本.在函数定义方面这样做是最好的,因为作为函数作者,您可以确保不实际保留副本.

在使用方面,您也可以标记它的范围:

void foo() {
    int a;
    void nested() {
        a++;
    }
    // this shouldn't allocate either
    scope void delegate() dg = &nested;
    some_function(&dg);
}
Run Code Online (Sandbox Code Playgroud)

所以,唯一的时间内存自动由GC分配时局部变量是由已经采取了地址的嵌套函数中使用没有scope关键字.

请注意,() => whatever() { return foo; }语法只是命名嵌套函数的简写,其地址是自动获取的,因此它们的工作方式与上面相同.dg = {a++;};dg = &nested;上面相同.

因此,从这个关键外卖为你的是,如果你想手动分配一个委托,你只需要手动分配一个对象,并从其中的一个方法,而不是自动捕获变量作出委托!但是,您需要跟踪生命周期并正确释放它.这是棘手的部分.

所以对于你的例子:

auto del = () {
  let res = f();
  cell.write(res);
};
Run Code Online (Sandbox Code Playgroud)

你可以把它翻译成:

 struct Helper {
     T res;
     void del() {
        cell.write(res);
     }
 }

 Helper* helper = malloc(Helper.sizeof);
 helper.res = res; // copy the local explicitly

 send(&helper.del);
Run Code Online (Sandbox Code Playgroud)

然后,在接收方,不要忘记free(dg.ptr);当你完成所以你不泄漏它.

或者,更好的是,如果您可以更改send为实际接受Helper对象,则根本不需要分配它,您可以按值传递它.


在我看来,你可以在该指针中打包一些其他数据以便就地传递其他数据,但这可能是黑客攻击并且可能是未定义的行为.试试看,如果你想玩:)