【问题标题】:Embedding Julia into C#: Garbage Collector Rewrite into C# Issues and Questions将 Julia 嵌入 C#:垃圾收集器重写为 C# 问题和问题
【发布时间】:2017-11-10 13:52:23
【问题描述】:

目前我正在编写一个可以调用用 Julia 模块编写的函数的 C# 脚本。 Julia 提供了一个 C API,允许在 Julia 中调用函数。我设法让用 Julia 模块编写的函数从 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 编写的内联函数。我对此有疑问,但这是另一个问题。

【问题讨论】:

  • 虽然原则上可能可行,但直接挂钩到 GC 堆栈可能并不理想。与 Julia GC 交互的另一种方法是将引用推送到全局数组中。这就是 pyjulia 所做的 (via PyCall),另请参阅手册 ccall 部分中的 closely related discussion。希望这足以开始。

标签: c# c garbage-collection interop julia


【解决方案1】:

问题 1

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

问题 2

Julia 不会释放任何东西,因为在创建 gc-frame 时不应该分配任何东西。如果要分配,通常最好通过 Julia 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 帧(以及异常帧)的设置和拆卸。

【讨论】:

  • 我没想到会有这么详细的答案。谢谢!我实际上是在尝试按照以赛亚的评论去做,但如果这不起作用,我会回到这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 1970-01-01
  • 2011-07-18
  • 2011-02-23
  • 2011-06-03
  • 1970-01-01
  • 2013-03-29
相关资源
最近更新 更多