Embedding Julia into C#: Garbage Collector Rewrite into C# 问题和疑问
Embedding Julia into C#: Garbage Collector Rewrite into C# Issues and Questions
目前我正在编写一个 C# 脚本,它可以调用用 Julia 模块编写的函数。 Julia 提供了一个 C API 允许在 Julia 中调用函数。我已经设法让用 Julia 模块编写的函数从 C# 调用,并让数组数据来回传递。
但是,我不太确定如何正确控制垃圾收集器。
这段代码是 julia.h 提供的内联代码,它告诉 Julia 垃圾收集器 args 指向的变量正在另一个脚本中使用,不应该 moved/deallocated。每次调用 (jl_gc_push()
或 jl_gc_push_args()
都会将一个东西推送到垃圾收集器使用的堆栈。
julia.h中的代码:
#define jl_pgcstack (jl_get_ptls_states()->pgcstack)
#define JL_GC_PUSH1(arg1) \
void *__gc_stkf[] = {(void*)3, jl_pgcstack, arg1}; \
jl_pgcstack = (jl_gcframe_t*)__gc_stkf;
...(similar functions for 2, 3, 4)............
#define JL_GC_PUSH5(arg1, arg2, arg3, arg4, arg5) \
void *__gc_stkf[] = {(void*)11, jl_pgcstack, arg1, arg2, arg3, arg4, arg5}; \
jl_pgcstack = (jl_gcframe_t*)__gc_stkf;
#define JL_GC_PUSHARGS(rts_var,n) \
rts_var = ((jl_value_t**)alloca(((n)+2)*sizeof(jl_value_t*)))+2; \
((void**)rts_var)[-2] = (void*)(((size_t)(n))<<1); \
((void**)rts_var)[-1] = jl_pgcstack; \
memset((void*)rts_var, 0, (n)*sizeof(jl_value_t*)); \
jl_pgcstack = (jl_gcframe_t*)&(((void**)rts_var)[-2])
#define JL_GC_POP() (jl_pgcstack = jl_pgcstack = jl_pgcstack->prev)
jl_get_ptls_states
returns 一个结构,它有一个名为 pgcstack
的指针。我相信这就是垃圾收集器使用的东西。 arg1
应该是 jl_value_t*
类型,rts_var
应该是 jl_value_t**
.
类型
问题 1:
我无法调和 JL_GC_PUSH1 中这一行(和其他 JL_GC_PUSH# 行)之间的特殊差异:
void *__gc_stkf[] = {(void*)3, ...
JL_GC_PUSHARGS 中的这一行:
((void**)rts_var)[-2] = (void*)(((size_t)(n))<<1);
如果我使用 JL_GC_PUSH1 告诉垃圾收集器我希望忽略一个变量,它会将数组中的第一个变量设置为 3。但是,如果我使用 JL_GC_PUSHARGS,它会将其设置为 2。我认为向左移动的位用零填充?不过,我了解其他所有功能在这些功能中是如何工作的。
问题二:
我正在编写一个 C# 函数来执行 JL_GC_PUSHARGS 所做的事情,除了它接受 params IntPtr
而不是 jl_value_t**
。如果我这样分配内存安全吗?有谁知道 Julia 是否会根据需要取消分配,或者我是否必须在内存上调用 Marshal.FreeHGlobal ?如果 Julia 仍然这样做并且我调用 Marshal.FreeHGlobal,会有问题吗?
C#版本:
public unsafe static void JL_GC_PUSHARGS(params IntPtr[] args) {
int l = args.Length;
IntPtr* pgcstacknew = (IntPtr*) Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * (l + 2)).ToPointer();
pgcstacknew[0] = (IntPtr)(2 * l + 1); //related to Question 1
pgcstacknew[1] = jl_pgcstack();
for(uint i = 2; i < l + 2; i++){
pgcstacknew[i] = args[i - 2];
}
jl_pgcstack() = pgcstacknew;
//I'm still having issues with this line ^^
}
现在假设 jl_pgcstack()
等同于用 C 编写的内联函数。我对此有疑问,但那是另一个问题。
Question 1
JL_GC_PUSH1
和 JL_GC_PUSHARGS
宏有不同的堆栈布局。低位表示是哪一位
Question 2
Julia 不会取消分配任何东西,因为在创建 gc-frame 时不应该分配任何东西。如果你要分配,通常最好通过 Julia API 并在 ObjectIdDict
(jl_eqtable_get/put).[=17= 之上构建一个模拟的引用计数方案。 ]
JL_GC_PUSHARGS 的直接翻译应如下所示:
unsafe {
// JL_GC_PUSHARGS
uint l = args.Length;
IntPtr* pgcstacknew = stackalloc IntPtr[l + 2];
pgcstacknew[0] = (IntPtr)(l << 2); // how many roots?
pgcstacknew[1] = jl_pgcstack(); // link to previous gc-frame
for (uint i = 0; i < l; i++) { // copy the args to the stack roots
pgcstacknew[i + 2] = args[i];
}
jl_pgcstack() = pgcstacknew; // install frame at top of gc-stack
}
// <do stuff with args here>
unsafe {
// JL_GC_POP
jl_pgcstack() = pgcstacknew[1]; // remove frame from gc-stack
}
另一种选择是使用 jl_call
函数集,其中包括 gc 框架(以及异常框架)的设置和拆卸。
目前我正在编写一个 C# 脚本,它可以调用用 Julia 模块编写的函数。 Julia 提供了一个 C API 允许在 Julia 中调用函数。我已经设法让用 Julia 模块编写的函数从 C# 调用,并让数组数据来回传递。
但是,我不太确定如何正确控制垃圾收集器。
这段代码是 julia.h 提供的内联代码,它告诉 Julia 垃圾收集器 args 指向的变量正在另一个脚本中使用,不应该 moved/deallocated。每次调用 (jl_gc_push()
或 jl_gc_push_args()
都会将一个东西推送到垃圾收集器使用的堆栈。
julia.h中的代码:
#define jl_pgcstack (jl_get_ptls_states()->pgcstack)
#define JL_GC_PUSH1(arg1) \
void *__gc_stkf[] = {(void*)3, jl_pgcstack, arg1}; \
jl_pgcstack = (jl_gcframe_t*)__gc_stkf;
...(similar functions for 2, 3, 4)............
#define JL_GC_PUSH5(arg1, arg2, arg3, arg4, arg5) \
void *__gc_stkf[] = {(void*)11, jl_pgcstack, arg1, arg2, arg3, arg4, arg5}; \
jl_pgcstack = (jl_gcframe_t*)__gc_stkf;
#define JL_GC_PUSHARGS(rts_var,n) \
rts_var = ((jl_value_t**)alloca(((n)+2)*sizeof(jl_value_t*)))+2; \
((void**)rts_var)[-2] = (void*)(((size_t)(n))<<1); \
((void**)rts_var)[-1] = jl_pgcstack; \
memset((void*)rts_var, 0, (n)*sizeof(jl_value_t*)); \
jl_pgcstack = (jl_gcframe_t*)&(((void**)rts_var)[-2])
#define JL_GC_POP() (jl_pgcstack = jl_pgcstack = jl_pgcstack->prev)
jl_get_ptls_states
returns 一个结构,它有一个名为 pgcstack
的指针。我相信这就是垃圾收集器使用的东西。 arg1
应该是 jl_value_t*
类型,rts_var
应该是 jl_value_t**
.
问题 1:
我无法调和 JL_GC_PUSH1 中这一行(和其他 JL_GC_PUSH# 行)之间的特殊差异:
void *__gc_stkf[] = {(void*)3, ...
JL_GC_PUSHARGS 中的这一行:
((void**)rts_var)[-2] = (void*)(((size_t)(n))<<1);
如果我使用 JL_GC_PUSH1 告诉垃圾收集器我希望忽略一个变量,它会将数组中的第一个变量设置为 3。但是,如果我使用 JL_GC_PUSHARGS,它会将其设置为 2。我认为向左移动的位用零填充?不过,我了解其他所有功能在这些功能中是如何工作的。
问题二:
我正在编写一个 C# 函数来执行 JL_GC_PUSHARGS 所做的事情,除了它接受 params IntPtr
而不是 jl_value_t**
。如果我这样分配内存安全吗?有谁知道 Julia 是否会根据需要取消分配,或者我是否必须在内存上调用 Marshal.FreeHGlobal ?如果 Julia 仍然这样做并且我调用 Marshal.FreeHGlobal,会有问题吗?
C#版本:
public unsafe static void JL_GC_PUSHARGS(params IntPtr[] args) {
int l = args.Length;
IntPtr* pgcstacknew = (IntPtr*) Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * (l + 2)).ToPointer();
pgcstacknew[0] = (IntPtr)(2 * l + 1); //related to Question 1
pgcstacknew[1] = jl_pgcstack();
for(uint i = 2; i < l + 2; i++){
pgcstacknew[i] = args[i - 2];
}
jl_pgcstack() = pgcstacknew;
//I'm still having issues with this line ^^
}
现在假设 jl_pgcstack()
等同于用 C 编写的内联函数。我对此有疑问,但那是另一个问题。
Question 1
JL_GC_PUSH1
和 JL_GC_PUSHARGS
宏有不同的堆栈布局。低位表示是哪一位
Question 2
Julia 不会取消分配任何东西,因为在创建 gc-frame 时不应该分配任何东西。如果你要分配,通常最好通过 Julia API 并在 ObjectIdDict
(jl_eqtable_get/put).[=17= 之上构建一个模拟的引用计数方案。 ]
JL_GC_PUSHARGS 的直接翻译应如下所示:
unsafe {
// JL_GC_PUSHARGS
uint l = args.Length;
IntPtr* pgcstacknew = stackalloc IntPtr[l + 2];
pgcstacknew[0] = (IntPtr)(l << 2); // how many roots?
pgcstacknew[1] = jl_pgcstack(); // link to previous gc-frame
for (uint i = 0; i < l; i++) { // copy the args to the stack roots
pgcstacknew[i + 2] = args[i];
}
jl_pgcstack() = pgcstacknew; // install frame at top of gc-stack
}
// <do stuff with args here>
unsafe {
// JL_GC_POP
jl_pgcstack() = pgcstacknew[1]; // remove frame from gc-stack
}
另一种选择是使用 jl_call
函数集,其中包括 gc 框架(以及异常框架)的设置和拆卸。