Julia 嵌入式 Julia

2018-08-12 21:26 更新

嵌入式 Julia

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

高級嵌入

我們從一個簡單的 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;
  }

編譯這個程序你需要把 Julia 的頭文件包含在路徑內(nèi)并且鏈接函數(shù)庫 libjulia。 比方說 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 完成,這個函數(shù)的參數(shù)是Julia安裝路徑,類型是 const char* 。如果沒有任何參數(shù),Julia 會自動尋找 Julia 的安裝路徑。

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

測試程序中的第三條語句使用 jl_eval_string 的調(diào)用評估了 Julia 語句。

類型轉(zhuǎn)換

真正的應(yīng)用程序不僅僅需要執(zhí)行表達(dá)式,而且返回主程序的值。jl_eval_string 返回 jl_value_t,它是一個指向堆上分配的 Julia 對象的指針。用這種方式存儲簡單的數(shù)據(jù)類型比如 Float64 叫做 boxing,提取存儲的原始數(shù)據(jù)叫做 unboxing。我們提升過的用 Julia 計算 2 的平方根和用 C 語言讀取結(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 是否是一個指定的 Julia 類型,我們可以使用 jl_is_... 函數(shù)。通過將 typeof(sqrt(2.0)) 輸入進 Julia shell,我們可以看到返回類型為 Float64(C 中的 double)。為了將裝好的 Julia 的值轉(zhuǎn)換成 C 語言中的 double,jl_unbox_float64 功能在上面的代碼片段中被使用。

相應(yīng)的 jl_box_... 功能被用來用另一種方式轉(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 語言來獲得 Julia 表達(dá)式的結(jié)果時,它不允許傳遞在 C 中計算的參數(shù)到 Julia 中。對于這個,你需要直接調(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 的處理通過調(diào)用 jl_get_function 檢索。第一個傳遞給 jl_get_function 的參數(shù)是一個指向 Base 模塊的指針,在那里 sqrt 被定義。然后,double 值使用 jl_box_float64 封裝。最后,在最后一步中,函數(shù)使用 jl_call1 被調(diào)用。jl_call0,jl_call2jl_call3 函數(shù)也存在,來方便地處理不同參數(shù)的數(shù)量。為了傳遞更多的參數(shù),使用 jl_call:

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

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

內(nèi)存管理

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

通常情況下,Julia 對象通過一個 garbage collector(GC) 來釋放,但是 GC 不會自動地知道我們在 C 中有一個對 Julia 值的引用。這意味著 GC 可以釋放指針,使指針無效。

GC 僅能在 Julia 對象被分配時運行。像 jl_box_float64 的調(diào)用運行分配,而且分配也能在任何運行 Julia 代碼的指針中發(fā)生。在 jl_... 調(diào)用間使用指針通常是安全的。但是為了確認(rèn)值能使 jl_... 調(diào)用生存,我們不得不告訴 Julia 我們有一個對 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 在棧上工作,所以它在棧幀被銷毀之前必須準(zhǔn)確地和 JL_GC_POP 成組。

幾個 Julia 值能使用 JL_GC_PUSH2,JL_GC_PUSH3,和 JL_GC_PUSH4 宏指令被立刻 push。為了 push 一個 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ù)來控制 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ù)。下一個例子將展示這是如此工作的。

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

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

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

    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ù)組你能生成一個對數(shù)據(jù)的簡單包裝:

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

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

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

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

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

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

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

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

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

訪問返回的數(shù)組

如果一個 Julia 函數(shù)返回一個數(shù)組,jl_eval_stringjl_call 的返回值能被轉(zhuǎn)換成一個 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ù)組的引用。

高維數(shù)組

Julia 的多維數(shù)組在內(nèi)存中以列的順序被存儲。這兒是一些創(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 時)以作為慣用的 C 代碼讀取。

異常

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

      jl_eval_string("this_function_does_not_exist()");

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

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

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

拋出 Julia 異常

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

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

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

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

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

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號