Gandiva 外部函數開發指南#

簡介#

Gandiva 作為分析表達式編譯器框架,透過外部函數擴展其功能。本指南重點在於協助開發人員理解、建立和整合外部函數到 Gandiva 中。外部函數是用戶定義的第三方函數,可用於 Gandiva 表達式中。

Gandiva 中外部函數類型概觀#

Gandiva 支援兩種主要類型的外部函數

  • C 函數:符合 C 呼叫慣例的函數。開發人員可以使用各種語言(如 C++、Rust、C 或 Zig)實作函數,並將其作為 C 函數公開給 Gandiva。

  • IR 函數:以 LLVM 中間表示法 (LLVM IR) 實作的函數。這些函數可以使用多種語言編寫,然後編譯成 LLVM IR 以在 Gandiva 中註冊。

為您的需求選擇正確的外部函數類型#

當將外部函數整合到 Gandiva 中時,選擇最符合您特定需求的類型至關重要。以下是 C 函數和 IR 函數之間的主要區別,以指導您的決策

  • C 函數
    • 語言彈性: C 函數提供彈性,讓您可以使用偏好的程式語言實作邏輯,然後將其作為 C 函數公開。

    • 廣泛適用性: 由於其相容性和易於整合,它們通常是廣泛用例的首選。

  • IR 函數
    • 建議的使用案例: IR 函數擅長處理不需要複雜邏輯或依賴複雜第三方函式庫的簡單任務。與 C 函數不同,IR 函數具有可內聯的優勢,這對於調用開銷構成重大支出的簡單操作尤其有利。此外,對於已經與 LLVM 工具鏈整合的專案,它們也是理想的選擇。

    • IR 編譯需求: 對於 IR 函數,整個實作(包括使用的任何第三方函式庫)都必須編譯成 LLVM IR。這可能會影響效能,特別是當相依函式庫很複雜時。

    • 功能限制: 某些進階功能(例如使用執行緒本地變數)在 IR 函數中不受支援。這是由於 Gandiva 內部使用的目前 JIT(即時)引擎的限制所致。

External C functions and IR functions integrating with Gandiva

外部函數註冊#

為了使函數可供 Gandiva 使用,您需要將其註冊為外部函數,並向 Gandiva 提供函數的中繼資料及其實作。

使用 NativeFunction 類別註冊中繼資料#

若要在 Gandiva 中註冊函數,請使用 gandiva::NativeFunction 類別。此類別擷取外部函數的簽章和中繼資料。

gandiva::NativeFunction 的建構子詳細資料

NativeFunction(const std::string& base_name, const std::vector<std::string>& aliases,
               const DataTypeVector& param_types, const DataTypePtr& ret_type,
               the ResultNullableType& result_nullable_type, std::string pc_name,
               int32_t flags = 0);

NativeFunction 類別用於定義外部函數的中繼資料。以下是其建構子參數的細目

  • base_name:函數的名稱,將在表達式中使用。

  • aliases:函數的替代名稱列表。

  • param_typesarrow::DataType 物件的向量,表示函數接受的參數類型。

  • ret_typestd::shared_ptr<arrow::DataType>,表示函數的傳回類型。

  • result_nullable_type:此參數指示結果是否可以為 null,基於輸入引數的 nullability。它可以採用下列其中一個值
    • ResultNullableType::kResultNullIfNull:結果有效性是子項有效性的交集。

    • ResultNullableType::kResultNullNever:結果始終有效。

    • ResultNullableType::kResultNullInternal:結果有效性取決於某些內部邏輯。

  • pc_name:對應的預編譯函數的名稱。* 通常,此名稱遵循慣例 {base_name} + _{param1_type} + {param2_type} + … + {paramN_type}。例如,如果基本名稱為 add,且函數採用兩個 int32 參數並傳回 int32,則預編譯函數名稱將為 add_int32_int32,但只要您可以保證其唯一性,此慣例並非強制性的。

  • flags:用於其他函數屬性的選用旗標(預設為 0)。請查看 NativeFunction::kNeedsContextNativeFunction::kNeedsFunctionHolderNativeFunction::kCanReturnErrors 以取得更多詳細資料。

函數註冊後,其實作需要透過 C 函數指標或 LLVM IR 函數提供。

外部 C 函數#

外部 C 函數可以使用不同的語言編寫,並作為 C 函數公開。與 Gandiva 的類型系統相容至關重要。

C 函數簽章#

簽章對應#

並非所有 Arrow 資料類型都在 Gandiva 中受支援。下表列出了 Gandiva 外部函數簽章類型與 C 函數簽章類型之間的對應關係

Gandiva 類型 (arrow 資料類型)

C 函數類型

int8

int8_t

int16

int16_t

int32

int32_t

int64

int64_t

uint8

uint8_t

uint16

uint16_t

uint32

uint32_t

uint64

uint64_t

float32

float

float64

double

boolean

bool

date32

int32_t

date64

int64_t

timestamp

int64_t

time32

int32_t

time64

int64_t

interval_month

int32_t

interval_day_time

int64_t

utf8 (作為參數類型)

const char*, uint32_t [請參閱下一節]

utf8 (作為傳回類型)

int64_t context, const char*, uint32_t* [請參閱下一節]

binary (作為參數類型)

const char*, uint32_t [請參閱下一節]

utf8 (作為傳回類型)

int64_t context, const char*, uint32_t* [請參閱下一節]

處理 arrow::StringType (utf8 類型) 和 arrow::BinaryType#

arrow::StringTypearrow::BinaryType 都是可變長度類型。它們在外部函數中的處理方式類似。由於 arrow::StringType (utf8 類型) 更常用,我們將在下面使用它作為範例來說明如何在外部函數中處理可變長度類型。

在外部函數中,使用 arrow::StringType (也稱為 utf8 類型) 作為函數參數或傳回值需要特殊處理。本節提供有關如何處理 arrow::StringType 的詳細資料。

作為參數

arrow::StringType 在函數簽章中用作參數類型時,應將對應的 C 函數定義為接受兩個參數

  • const char*:此參數作為字串資料的指標。

  • uint32_t:此參數表示字串資料的長度。

作為傳回類型

arrow::StringType (utf8 類型) 在函數簽章中用作傳回類型時,有幾個具體注意事項適用

  1. NativeFunction 中繼資料旗標: * 此函數的 NativeFunction 中繼資料必須包含 NativeFunction::kNeedsContext 旗標。此旗標對於確保函數中的正確上下文管理至關重要。

  2. 函數參數
    • 上下文參數:C 函數應以額外的參數 int64_t context 開頭。此參數對於函數內的上下文管理至關重要。

    • 字串長度輸出參數:函數也應在結尾包含 uint32_t* 參數。此輸出參數將儲存傳回的字串資料的長度。

  3. 傳回值:函數應傳回 const char* 指標,指向字串資料。

  4. 函數實作: * 記憶體配置和錯誤訊息傳遞: 在函數的實作中,分別使用 gdv_fn_context_arena_mallocgdv_fn_context_set_error_msg 進行記憶體配置和錯誤訊息傳遞。這兩個函數都將 int64_t context 作為其第一個參數,以便於有效利用上下文。

外部 C 函數註冊 API#

您可以使用 gandiva::FunctionRegistry 的 API 註冊外部 C 函數

/// \brief register a C function into the function registry
/// @param func the registered function's metadata
/// @param c_function_ptr the function pointer to the
/// registered function's implementation
/// @param function_holder_maker this will be used as the function holder if the
/// function requires a function holder
arrow::Status Register(
    NativeFunction func, void* c_function_ptr,
    std::optional<FunctionHolderMaker> function_holder_maker = std::nullopt);

上述 API 允許您註冊外部 C 函數。

  • NativeFunction 物件描述外部 C 函數的中繼資料。

  • c_function_ptr 是外部 C 函數實作的函數指標。

  • 如果外部 C 函數需要函數持有者,則選用的 function_holder_maker 用於為外部 C 函數建立函數持有者。查看 gandiva::FunctionHolder 類別及其數個子類別以取得更多詳細資料。

外部 IR 函數#

IR 函數實作#

Gandiva 對 IR (中間表示法) 函數的支援提供了彈性,可根據您的特定需求使用各種程式語言來實作這些函數。

編譯範例和工具#
  1. 使用 C++ 或 C

    • 如果您的 IR 函數是以 C++ 或 C 實作的,則可以將它們編譯成 LLVM 位元碼,這是 Gandiva 理解的中間表示法。

    • 使用 Clang 編譯:對於 C++ 實作,您可以將 clang 與 -emit-llvm 選項搭配使用。此方法會將您的 IR 函數直接編譯成 LLVM 位元碼,使其準備好與 Gandiva 整合。

  2. 與 CMake 整合

    • 在將 C++ 與 CMake 一起使用的專案中,請考慮利用 Arrow 儲存庫中的 GandivaAddBitcode.cmake 模組。此模組可以簡化將自訂位元碼新增至 Gandiva 的流程。

參數和傳回類型的一致性#

維持與 C 函數中建立的參數和傳回類型的一致性非常重要。遵守上一節中討論的規則可確保與 Gandiva 的類型系統相容。

在 Gandiva 中註冊外部 IR 函數#

  1. 實作和編譯後

    成功實作並將您的 IR 函數編譯成 LLVM 位元碼後,下一個關鍵步驟是在 Gandiva 內註冊它們。

  2. 使用 Gandiva 的 FunctionRegistry API

    Gandiva 在 gandiva::FunctionRegistry 類別中提供特定的 API,以促進此註冊流程。

    註冊 API

    • 從位元碼檔案註冊

      // Registers a set of functions from a specified bitcode file
      arrow::Status Register(const std::vector<NativeFunction>& funcs,
                             const std::string& bitcode_path);
      
    • 從位元碼緩衝區註冊

      // Registers a set of functions from a bitcode buffer
      arrow::Status Register(const std::vector<NativeFunction>& funcs,
                             std::shared_ptr<arrow::Buffer> bitcode_buffer);
      

    重點

    • 這些 API 旨在註冊一系列外部 IR 函數,可以從指定的位元碼檔案或預先載入的位元碼緩衝區註冊。

    • 務必確保位元碼檔案或緩衝區包含正確編譯的 IR 函數。

    • NativeFunction 實例在此流程中扮演至關重要的角色,用於定義每個要註冊的外部 IR 函數的中繼資料。

結論#

本指南概述了將外部函數整合到 Gandiva 中的詳細步驟。它涵蓋了 C 函數和 IR 函數,以及它們在 Gandiva 中的註冊。對於更複雜的場景,請參閱 Gandiva 的文件和原始碼中的範例實作。