d - 是否可以避免代表的 GC?

标签 d

是否可以避免代表的 GC?

我正在构建一个任务系统。我有带有本地任务队列的 N 线程。任务队列基本上只是一个 Array!Fiber 任务。因为不鼓励将纤程发送到不同的线程,所以我将关闭/委托(delegate)发送到线程,从该委托(delegate)创建纤程并将其放入数组 tasks

现在我发送的委托(delegate)是捕获变量的委托(delegate)。

//Some Pseudo code

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

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

send(del);

}

现在单元被堆分配并与原子计数器同步。然后我可以检查来自 cell 的原子计数器是否已达到 0,如果达到了,我可以安全地从中读取。

问题在于捕获变量的委托(delegate)在 GC 上分配变量。现在我只分配一个指针,这可能不是一个大问题,但我仍然想避免 GC。

我该怎么做?

最佳答案

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

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

struct d_delegate {
    void* ptr; // yes, it is actually typed void*!
    T* funcptr; // this is actually a function pointer
};

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

那个void*是指向数据的,就像切片一样,它可以来自不同的地方:

Object obj = new Object();
string delegate() dg = &obj.toString;

此时,dg.ptr指向obj,恰好是一个垃圾回收的类对象,不过只是因为我new在上面编辑。

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

MyStruct obj;

string delegate() dg = &obj.doSomething;

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

无论某物是否是委托(delegate),都没有说明用于它的内存分配方案 - 这可以说是危险的,因为传递给您的委托(delegate)可能指向一个临时对象,该对象将在您完成之前消失! (这是顺便使用 GC 的主要原因,以帮助防止此类 use-after-free 错误。)

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

void some_function(void delegate() dg);

void foo() {
    int a;
    void nested() {
        a++;
    }
    some_function(&nested);
}

在这里,编译器会将变量 a 复制到 GC 段,因为它假定 some_function 将保留它的副本并希望防止 use-after-free 错误(调试起来很痛苦,因为它经常导致内存损坏!)以及内存泄漏。

但是,如果您向编译器保证您将通过在委托(delegate)定义中使用 scope 关键字自己正确地完成它,它将信任您并将本地人留在他们所在的位置:

void some_function(scope void delegate() dg);

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

不过,在使用方面,您也可以将其标记为范围:

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

因此,GC 自动分配内存的唯一时间是当局部变量被嵌套函数使用时 没有 scope 关键字。

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

因此,对您来说,关键的收获是,如果您想手动分配一个委托(delegate),您只需要手动分配一个对象并从其方法之一创建一个委托(delegate),而不是自动捕获变量!但是,您需要跟踪生命周期并正确释放它。这是棘手的部分。

所以对于你的例子:

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

你可以把它翻译成:

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

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

 send(&helper.del);

然后,在接收端,完成后不要忘记 free(dg.ptr); 以免泄漏。

或者,更好的是,如果您可以将 send 更改为仅实际获取 Helper 对象,则根本不需要分配它,只需传递它即可按值(value)。


我还想到,您可以在该指针中打包一些其他数据以就地传递其他数据,但这将是 abi hacking 并且可能是未定义的行为。如果你想玩,试试吧:)

关于d - 是否可以避免代表的 GC?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37841207/

相关文章:

d - 重写 D 中的 'package' 成员函数

linux - 开关中断时 D 语言中的随机段错误

语: how to enforce an interface on a template function

d - 如何将 ref/out 函数指针传递给函数?

d - 使用 GDC 链接到 DerelictGL3

d - 无法编译一些简单的 D 代码

d - 有效D : best practices and design patterns

interop - 与 C 版本 SDL2 链接时对 D 隐藏控制台

c++ - 如何正确从 D 接口(interface)到 C++?

d - 如何仅为 WAF 中的库文件之一添加编译选项?