是否有可能避免代表的 GC?
Is it possible to avoid the GC for delegates?
是否可以避免委托的 GC?
我正在构建一个任务系统。我有带有本地任务队列的 N-Threads。一个任务队列基本上就是一个Array!Fiber tasks
。因为不鼓励将纤程发送到不同的线程,所以我将 closure/delegate 发送到线程,从该委托创建纤程并将其放入数组 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。
我该怎么做?
您可能已经知道所有这些,但这是一个常见问题解答,所以我打算写一些细节。
首先,让我们了解一下委托是什么。就像切片只是一个与长度配对的 C 数据指针一样,委托只是一个与函数指针配对的 C 数据指针。这些被一起传递给期望它们的函数,就好像它被定义了一样
struct d_delegate {
void* ptr; // yes, it is actually typed void*!
T* funcptr; // this is actually a function pointer
};
(请注意,当您尝试在 class 方法中使用嵌套委托时,其中只有一个数据指针这一事实是一些编译器错误背后的原因!)
void*
是指向数据的东西,就像切片一样,它可以来自各种地方:
Object obj = new Object();
string delegate() dg = &obj.toString;
此时,dg.ptr
指向 obj
,它恰好是一个垃圾回收的 class 对象,但这只是因为我在上面 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; }
语法只是 shorthand 用于命名嵌套函数,其地址会自动获取,因此它们的工作方式与上述相同。 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
个对象,则根本不需要分配它,您可以按值传递它。
我还想到您可以在该指针中打包一些其他数据以就地传递其他数据,但那会是 abi hacking 并且可能是未定义的行为。如果你想玩的话试试吧:)
是否可以避免委托的 GC?
我正在构建一个任务系统。我有带有本地任务队列的 N-Threads。一个任务队列基本上就是一个Array!Fiber tasks
。因为不鼓励将纤程发送到不同的线程,所以我将 closure/delegate 发送到线程,从该委托创建纤程并将其放入数组 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。
我该怎么做?
您可能已经知道所有这些,但这是一个常见问题解答,所以我打算写一些细节。
首先,让我们了解一下委托是什么。就像切片只是一个与长度配对的 C 数据指针一样,委托只是一个与函数指针配对的 C 数据指针。这些被一起传递给期望它们的函数,就好像它被定义了一样
struct d_delegate {
void* ptr; // yes, it is actually typed void*!
T* funcptr; // this is actually a function pointer
};
(请注意,当您尝试在 class 方法中使用嵌套委托时,其中只有一个数据指针这一事实是一些编译器错误背后的原因!)
void*
是指向数据的东西,就像切片一样,它可以来自各种地方:
Object obj = new Object();
string delegate() dg = &obj.toString;
此时,dg.ptr
指向 obj
,它恰好是一个垃圾回收的 class 对象,但这只是因为我在上面 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; }
语法只是 shorthand 用于命名嵌套函数,其地址会自动获取,因此它们的工作方式与上述相同。 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
个对象,则根本不需要分配它,您可以按值传递它。
我还想到您可以在该指针中打包一些其他数据以就地传递其他数据,但那会是 abi hacking 并且可能是未定义的行为。如果你想玩的话试试吧:)