将Julia嵌入到C#中:将垃圾收集器重写到C#问题和问题

问题描述:

当前我正在编写一个可以调用用Julia模块编写的函数的C#脚本。 Julia提供了一个C API,允许在Julia中调用函数。我设法使用C#调用Julia模块编写的函数,并获取数组数据来回传递。将Julia嵌入到C#中:将垃圾收集器重写到C#问题和问题

但是,我不完全知道如何正确控制垃圾回收器。 此代码是由julia.h提供的内联代码,它告诉Julia垃圾收集器args指向的变量正在另一个脚本中使用,不应被移动/释放。每次调用(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返回有一个名为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.我认为这个位转移到左边填补与零?我理解这些功能其他方面的工作原理。

问题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写我有与该问题的内联函数,但是这是一个不同的问题。

+0

虽然可能会在原则上,直接挂接到GC堆可能是不理想的。与Julia GC交互的另一种方式是将引用推入全局数组中。这是pyjulia做什么([通过PyCall(https://github.com/JuliaPy/PyCall.jl/blob/1d755f27fd440a43b9a792919fee0531495754e0/src/pytype.jl#L433-L440)),并且也看到了[密切相关的讨论] (https://docs.julialang.org/en/latest/manual/calling-c-and-fortran-code/#Garbage-Collection-Safety-1)在手动的ccall一部分。希望这足以开始。 –

问题1

JL_GC_PUSH1JL_GC_PUSHARGS宏有不同的堆栈布局。低位表示它是哪一个。

问题2

朱莉亚不会取消分配任何东西,因为是不应该建立一个GC-框架时要什么进行分配。如果你打算分配,通常最好要经过朱莉娅API和一个ObjectIdDict(jl_eqtable_get/PUT)之上构建一个模拟的裁判计数方案。

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帧的建立和拆除(以及一个异常帧) 。

+0

我没想到这样一个详细的解答。谢谢!实际上,我是想做什么以赛亚评论,但如果不行,我会回到这一点。 –