Julia 嵌入式 Julia

2018-08-12 21:26 更新

嵌入式 Julia

我們已經(jīng)知道 調(diào)用 C 和 Fortran 代碼 Julia 可以用簡(jiǎn)單有效的方式調(diào)用 C 函數(shù)。但是有很多情況下正好相反:需要從 C 調(diào)用 Julia 函數(shù)。這可以把 Julia 代碼整合到更大型的 C/C++ 項(xiàng)目中去, 而不需要重新把所有都用 C/C++ 寫(xiě)一遍。 Julia 提供了給 C 的 API 來(lái)實(shí)現(xiàn)這一點(diǎn)。正如大多數(shù)語(yǔ)言都有方法調(diào)用 C 函數(shù)一樣, Julia的 API 也可以用于搭建和其他語(yǔ)言之間的橋梁。

高級(jí)嵌入

我們從一個(gè)簡(jiǎn)單的 C 程序入手,它初始化 Julia 并且調(diào)用一些 Julia 的代碼::

  #include <julia.h>

  int main(int argc, char *argv[])
  {
      jl_init(NULL);
      JL_SET_STACK_BASE;

      jl_eval_string("print(sqrt(2.0))");

      return 0;
  }

編譯這個(gè)程序你需要把 Julia 的頭文件包含在路徑內(nèi)并且鏈接函數(shù)庫(kù) libjulia。 比方說(shuō) Julia 安裝在 $JULIA_DIR, 就可以用 gcc 編譯::

    gcc -o test -I$JULIA_DIR/include/julia -L$JULIA_DIR/usr/lib -ljulia test.c

或者可以看看 Julia 源碼里 example/ 下的 embedding.c。

調(diào)用Julia函數(shù)之前要先初始化 Julia, 可以用 jl_init 完成,這個(gè)函數(shù)的參數(shù)是Julia安裝路徑,類(lèi)型是 const char* 。如果沒(méi)有任何參數(shù),Julia 會(huì)自動(dòng)尋找 Julia 的安裝路徑。

第二個(gè)語(yǔ)句初始化了 Julia 的任務(wù)調(diào)度系統(tǒng)。這條語(yǔ)句必須在一個(gè)不返回的函數(shù)中出現(xiàn),只要 Julia 被調(diào)用(main 運(yùn)行得很好)。嚴(yán)格地講,這條語(yǔ)句是可以選擇的,但是轉(zhuǎn)換任務(wù)的操作將引起問(wèn)題,如果它被省略的話(huà)。

測(cè)試程序中的第三條語(yǔ)句使用 jl_eval_string 的調(diào)用評(píng)估了 Julia 語(yǔ)句。

類(lèi)型轉(zhuǎn)換

真正的應(yīng)用程序不僅僅需要執(zhí)行表達(dá)式,而且返回主程序的值。jl_eval_string 返回 jl_value_t,它是一個(gè)指向堆上分配的 Julia 對(duì)象的指針。用這種方式存儲(chǔ)簡(jiǎn)單的數(shù)據(jù)類(lèi)型比如 Float64 叫做 boxing,提取存儲(chǔ)的原始數(shù)據(jù)叫做 unboxing。我們提升過(guò)的用 Julia 計(jì)算 2 的平方根和用 C 語(yǔ)言讀取結(jié)果的樣本程序如下所示:

    jl_value_t *ret = jl_eval_string("sqrt(2.0)");

    if (jl_is_float64(ret)) {
        double ret_unboxed = jl_unbox_float64(ret);
        printf("sqrt(2.0) in C: %e \n", ret_unboxed);
    }

為了檢查 ret 是否是一個(gè)指定的 Julia 類(lèi)型,我們可以使用 jl_is_... 函數(shù)。通過(guò)將 typeof(sqrt(2.0)) 輸入進(jìn) Julia shell,我們可以看到返回類(lèi)型為 Float64(C 中的 double)。為了將裝好的 Julia 的值轉(zhuǎn)換成 C 語(yǔ)言中的 double,jl_unbox_float64 功能在上面的代碼片段中被使用。

相應(yīng)的 jl_box_... 功能被用來(lái)用另一種方式轉(zhuǎn)換:

    jl_value_t *a = jl_box_float64(3.0);
    jl_value_t *b = jl_box_float32(3.0f);
    jl_value_t *c = jl_box_int32(3);

正如我們下面將看到的,調(diào)用帶有指定參數(shù)的 Julia 函數(shù)裝箱(boxing)是需要的。

調(diào)用 Julia 的函數(shù)

當(dāng) jl_eval_string 允許 C 語(yǔ)言來(lái)獲得 Julia 表達(dá)式的結(jié)果時(shí),它不允許傳遞在 C 中計(jì)算的參數(shù)到 Julia 中。對(duì)于這個(gè),你需要直接調(diào)用 Julia 函數(shù),使用 jl_call:

    jl_function_t *func = jl_get_function(jl_base_module, "sqrt");
    jl_value_t *argument = jl_box_float64(2.0);
    jl_value_t *ret = jl_call1(func, argument);

在第一步中,Julia 函數(shù) sqrt 的處理通過(guò)調(diào)用 jl_get_function 檢索。第一個(gè)傳遞給 jl_get_function 的參數(shù)是一個(gè)指向 Base 模塊的指針,在那里 sqrt 被定義。然后,double 值使用 jl_box_float64 封裝。最后,在最后一步中,函數(shù)使用 jl_call1 被調(diào)用。jl_call0,jl_call2jl_call3 函數(shù)也存在,來(lái)方便地處理不同參數(shù)的數(shù)量。為了傳遞更多的參數(shù),使用 jl_call:

    jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs)

第二個(gè)參數(shù) args 是一個(gè) jl_value_t* 參數(shù)的數(shù)組而且 nargs 是參數(shù)的數(shù)字。

內(nèi)存管理

正如我們已經(jīng)看見(jiàn)的,Julia 對(duì)象作為指針在 C 中呈現(xiàn)。這引出了一個(gè)問(wèn)題,誰(shuí)應(yīng)該釋放這些對(duì)象。

通常情況下,Julia 對(duì)象通過(guò)一個(gè) garbage collector(GC) 來(lái)釋放,但是 GC 不會(huì)自動(dòng)地知道我們?cè)?C 中有一個(gè)對(duì) Julia 值的引用。這意味著 GC 可以釋放指針,使指針無(wú)效。

GC 僅能在 Julia 對(duì)象被分配時(shí)運(yùn)行。像 jl_box_float64 的調(diào)用運(yùn)行分配,而且分配也能在任何運(yùn)行 Julia 代碼的指針中發(fā)生。在 jl_... 調(diào)用間使用指針通常是安全的。但是為了確認(rèn)值能使 jl_... 調(diào)用生存,我們不得不告訴 Julia 我們有一個(gè)對(duì) Julia 值的引用。這可以使用 JL_GC_PUSH 宏指令完成。This can be done using the JL_GC_PUSH macros:

    jl_value_t *ret = jl_eval_string("sqrt(2.0)");
    JL_GC_PUSH1(&ret);
    // Do something with ret
    JL_GC_POP();

JL_GC_POP 調(diào)用釋放了之前 JL_GC_PUSH 建立的引用。注意到 JL_GC_PUSH 在棧上工作,所以它在棧幀被銷(xiāo)毀之前必須準(zhǔn)確地和 JL_GC_POP 成組。

幾個(gè) Julia 值能使用 JL_GC_PUSH2JL_GC_PUSH3,和 JL_GC_PUSH4 宏指令被立刻 push。為了 push 一個(gè) Julia 值的數(shù)組我們可以使用 JL_GC_PUSHARGS 宏指令,它能向以下那樣使用:cro, which can be used as follows:

    jl_value_t **args;
    JL_GC_PUSHARGS(args, 2); // args can now hold 2 `jl_value_t*` objects
    args[0] = some_value;
    args[1] = some_other_value;
    // Do something with args (e.g. call jl_... functions)
    JL_GC_POP();

控制垃圾回收

有一些函數(shù)來(lái)控制 GC。在普通的使用案例中,這些不應(yīng)該是必需的。

void jl_gc_collect() Force a GC run
void jl_gc_disable() Disable the GC
void jl_gc_enable() Enable the GC

處理數(shù)組

Julia 和 C 能不用復(fù)制而分享數(shù)組數(shù)據(jù)。下一個(gè)例子將展示這是如此工作的。

Julia 數(shù)組通過(guò)數(shù)據(jù)類(lèi)型 jl_array_t* 用 C 語(yǔ)言顯示?;旧?,jl_array_t 是一個(gè)包含以下的結(jié)構(gòu):

  • 有關(guān)數(shù)據(jù)類(lèi)型的信息
  • 指向數(shù)據(jù)塊的指針
  • 有關(guān)數(shù)組大小的信息

為了使事情簡(jiǎn)單,我們用一個(gè) 1D 數(shù)組開(kāi)始。創(chuàng)建一個(gè)包含長(zhǎng)度為 10 個(gè)元素的 Float64 數(shù)組通過(guò)以下完成:

    jl_value_t* array_type = jl_apply_array_type(jl_float64_type, 1);
    jl_array_t* x          = jl_alloc_array_1d(array_type, 10);

或者,如果你已經(jīng)分配了數(shù)組你能生成一個(gè)對(duì)數(shù)據(jù)的簡(jiǎn)單包裝:

double *existingArray = (double*)malloc(sizeof(double)*10);
jl_array_t *x = jl_ptr_to_array_1d(array_type, existingArray, 10, 0);

最后一個(gè)參數(shù)是一個(gè)表明 Julia 是否應(yīng)該獲取數(shù)據(jù)所有權(quán)的布爾值。如果這個(gè)參數(shù)非零,GC 將在數(shù)組不再引用時(shí)在數(shù)據(jù)指針上調(diào)用 free。

為了獲取 x 的數(shù)據(jù),我們可以使用 jl_array_data:

    double *xData = (double*)jl_array_data(x);

現(xiàn)在我們可以填寫(xiě)數(shù)組:

    for(size_t i=0; i<jl_array_len(x); i++)
        xData[i] = i;

現(xiàn)在讓我們調(diào)用一個(gè)在 x 上運(yùn)行操作的 Julia 函數(shù):

    jl_function_t *func  = jl_get_function(jl_base_module, "reverse!");
    jl_call1(func, (jl_value_t*)x);

通過(guò)打印數(shù)組,我們可以核實(shí) x 的元素現(xiàn)在被顛倒了。

訪(fǎng)問(wèn)返回的數(shù)組

如果一個(gè) Julia 函數(shù)返回一個(gè)數(shù)組,jl_eval_stringjl_call 的返回值能被轉(zhuǎn)換成一個(gè) jl_array_t*:

    jl_function_t *func  = jl_get_function(jl_base_module, "reverse");
    jl_array_t *y = (jl_array_t*)jl_call1(func, (jl_value_t*)x);

現(xiàn)在 y 的內(nèi)容能在使用 jl_array_data 前被獲取。一如往常,當(dāng)它在使用中時(shí)確保保持對(duì)數(shù)組的引用。

高維數(shù)組

Julia 的多維數(shù)組在內(nèi)存中以列的順序被存儲(chǔ)。這兒是一些創(chuàng)建二維數(shù)組和獲取屬性的代碼:

    // Create 2D array of float64 type
    jl_value_t *array_type = jl_apply_array_type(jl_float64_type, 2);
    jl_array_t *x  = jl_alloc_array_2d(array_type, 10, 5);

    // Get array pointer
    double *p = (double*)jl_array_data(x);
    // Get number of dimensions
    int ndims = jl_array_ndims(x);
    // Get the size of the i-th dim
    size_t size0 = jl_array_dim(x,0);
    size_t size1 = jl_array_dim(x,1);

    // Fill array with data
    for(size_t i=0; i<size1; i++)
        for(size_t j=0; j<size0; j++)
            p[j + size0*i] = i + j;

注意到當(dāng) Julia 數(shù)組使用 1-based 的索引,C 的 API 使用 0-based 的索引(例如在調(diào)用 jl_array_dim 時(shí))以作為慣用的 C 代碼讀取。

異常

Julia 代碼能拋出異常。比如,考慮以下:

      jl_eval_string("this_function_does_not_exist()");

這個(gè)調(diào)用將什么都不做。但是,檢查一個(gè)異常是否拋出是可能的。

    if (jl_exception_occurred())
        printf("%s \n", jl_typeof_str(jl_exception_occurred()));

如果你用一個(gè)支持異常的語(yǔ)言(比如,Python,C#,C++)使用 Julia C API,用一個(gè)檢查異常是否被拋出的函數(shù)包裝每一個(gè)調(diào)用 libjulia 的調(diào)用是有道理的,而且它用主語(yǔ)言重新拋出異常。

拋出 Julia 異常

當(dāng)寫(xiě)一個(gè)可調(diào)用的 Julia 函數(shù)時(shí),驗(yàn)證參數(shù)和拋出異常來(lái)指出錯(cuò)誤是必要的。一個(gè)典型的類(lèi)型檢查像這樣:

    if (!jl_is_float64(val)) {
        jl_type_error(function_name, (jl_value_t*)jl_float64_type, val);
    }

通常的異常能使用函數(shù)來(lái)引起:

    void jl_error(const char *str);
    void jl_errorf(const char *fmt, ...);

jl_error 使用一個(gè) C 的字符串,jl_errorfprintf 一樣被調(diào)用:

    jl_errorf("argument x = %d is too large", x);

在這個(gè)例子中 x 被假設(shè)為一個(gè)整型。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)