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

时间:2016-06-15 16:43:05

标签: 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);

}

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

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

我该怎么做?

1 个答案:

答案 0 :(得分:8)

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

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

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

(请注意,当你尝试在类方法中使用嵌套委托时,有一个数据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也指向该临时对象。

某些东西是否是一个代表没有说明用于它的内存分配方案 - 这可能是危险的,因为传递给你的委托可能指向一个临时对象,它将在你完成它之前消失! (这就是为什么要使用GC的主要原因,以帮助防止这种免费使用后的错误。)

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

void some_function(void delegate() dg);

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

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

但是,如果您向编译器承诺,您可以通过在委托定义中使用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关键字。

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

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

所以对你的例子来说:

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个对象,则根本不需要分配它,您只需按值传递即可。< / p>

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