Athena 執行引擎:在線服務計算的效率王者

引言

在在線服務領域,計算任務呈現出獨特的特性:一方面,數據量通常不會過于龐大,因為在線服務對耗時和響應速度有著嚴苛要求;另一方面,計算任務具有可控性,其大多并非由用戶實時輸入動態生成,屬于有限集合,因此能夠進行預編譯處理。在這樣的背景下,傳統的向量化引擎如 velox,可能會因數據在行存與列存之間轉換產生的額外開銷,導致性能不增反降;而解釋性引擎也無法充分發揮預編譯帶來的效率優勢。
athena 執行引擎正是為了在上述場景中實現極致性能而誕生。此前筆者介紹的 jitfusion 引擎:https://blog.csdn.net/qq_34262582/article/details/145496431?spm=1001.2014.3001.5501。
在列表類型計算和優化方面存在不足,且缺乏便捷的類腳本語言描述執行過程。經過持續完善與優化,athena 應運而生,用戶能夠通過簡潔的 DSL 描述執行邏輯。本文將深入剖析 athena 的設計架構、核心優化特性,并通過嚴謹的 benchmark 對比,展現其相較于 exprtk 和 gandiva 的性能優勢。

設計架構:靈活接口與簡潔 DSL

接口設計

首先 athena 提供的對外接口是這樣的。

  // Applicable to simple scenarios, the program will not actually use a custom store function to write data. Instead,// the result will be returned, similar to expression scenarios.// If you need to optimize the memory allocation issue of ExecContext, you can use the function passed to ExecContext.Status Compile(const std::string& code, const std::unique_ptr<FunctionRegistry>& func_registry);Status Execute(void* entry_arguments, RetType* result);Status Execute(ExecContext& exec_ctx, void* entry_arguments, RetType* result);// Applicable to complex scenarios where multiple pipelines are computed simultaneously. Each pipeline writes data// using a custom function, and results are not returned. This is similar to feature processing scenarios.// If you need to optimize the memory allocation issue of ExecContext, you can use the function passed to ExecContext.Status Compile(const std::vector<std::string>& code, const std::unique_ptr<FunctionRegistry>& func_registry);Status Execute(void* entry_arguments, void* result);Status Execute(ExecContext& exec_ctx, void* entry_arguments, void* result);

其中,Compile接口負責編譯 DSL 代碼,只有完成編譯后,才能通過 Execute 接口執行任務,且 Execute 接口具備線程安全特性。code 為 DSL 代碼,func_registry 用于函數注冊,entry_arguments 接收用戶輸入,result 存儲輸出結果,exec_ctx 則作為執行上下文,默認情況下即使不傳入也會自動生成。

這個設計有幾個好處。

?1.通過傳入 func_registry,可避免重復的函數注冊操作,適用于函數注冊相對固定的服務場景。
?2.用戶能夠自由定義輸入輸出,無需按照引擎規則重組數據,從而有效降低執行成本。
?3.用戶可通過傳入 exec_ctx,實現自定義的內存池化邏輯,減少頻繁內存分配帶來的性能損耗。
?4.支持同時編譯多個計算 pipeline,能夠自動識別并優化重復計算路徑,尤其適用于特征工程等復雜場景。

當用戶使用第一組函數來執行時,result 會得到最后一行代碼返回的結果。使用第二組函數來執行時,result 需要用戶調用自定義的函數來把結果寫到傳入的 result 指針,此時無法通過最后一行代碼返回得到結果。

DSL

athena 的 DSL 遵循簡潔易用的設計原則,其核心規則如下:

?1.執行過程由 statement 組成,每個 statement 的分隔符是’;'號。
?2.statement 的格式必須按以下方式構造:{ID} = {Expression},其中 ID 表示變量名,Expression 是一個表達式。
?3.除了支持各種運算操作外,表達式還支持幾種特殊語法。函數語法:{function_name}({arg1}, {arg2}, …)。它還支持 switch 語句和 if 語句。遵循簡潔原則,switch 語句和 if 語句的語法與函數語法類似:if({condition}, {true_expression}, {false_expression}),switch({case1}, {value1}, {case2}, {value2}…, {default_value})。
?4.用戶可通過 entry_arg 訪問輸入參數指針,exec_ctx 訪問執行上下文,output 訪問輸出參數指針。

核心優化:性能提升的關鍵

athena 內部有很多優化,下面來一一講解。

Constant folding

athena 會在編譯階段自動計算可確定的常量表達式。例如:

int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);std::string code = R"(r = 2 * 3 + 4;)";std::vector<double> r(3);auto st = athena.Compile(code, func_registry);athena::RetType ret;athena.Execute(nullptr, &ret);std::cout << std::get<int32_t>(ret) << "\n";return 0;
}

計算 2 * 3 + 4, 得到的中間代碼是這樣的。

; ModuleID = 'module'
source_filename = "module"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
define noundef i32 @entry(ptr noalias nocapture readonly %0, ptr noalias nocapture readnone %1, ptr noalias nocapture readnone %2) local_unnamed_addr #0 {
entryBB:ret i32 10
}attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }

編譯后的中間代碼直接返回結果10,避免了運行時的重復計算。

Dead code elimination

引擎能夠識別并刪除對最終結果無影響的代碼。比如:

int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);std::string code = R"(a = 2 * 3 + 4;b = 100 * 100;c = a * 2;)";std::vector<double> r(3);auto st = athena.Compile(code, func_registry);athena::RetType ret;athena.Execute(nullptr, &ret);std::cout << std::get<int32_t>(ret) << "\n";return 0;
}

由于僅最后一行代碼的結果被返回,“b = 100 * 100;” 被認定為死代碼,編譯時自動剔除。

; ModuleID = 'module'
source_filename = "module"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
define noundef i32 @entry(ptr noalias nocapture readonly %0, ptr noalias nocapture readnone %1, ptr noalias nocapture readnone %2) local_unnamed_addr #0 {
entryBB:ret i32 20
}attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }

Static Typing Language

athena 的 DSL 作為靜態類型語言,athena 在編譯期確定所有變量類型,能夠進行嚴格的類型安全檢查。

比如說除0。此時編譯會失敗,輸出錯誤信息。

int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);std::string code = R"(a = 1 / 0;)";std::vector<double> r(3);auto st = athena.Compile(code, func_registry);std::cout << st.ToString() << std::endl;return 0;
}
Parse Error: Cant no div/mod zero

或者是浮點數位運算。

int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);std::string code = R"(a = 1.0 & 2.0;)";std::vector<double> r(3);auto st = athena.Compile(code, func_registry);std::cout << st.ToString() << std::endl;return 0;
}
Runtime Error: Module verification failed: Logical operators only work with integral types!%3 = and double 1.000000e+00, 2.000000e+00

又或者是函數調用的時候類型不匹配。

int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);std::string code = R"(a = Len(1.0);)";std::vector<double> r(3);auto st = athena.Compile(code, func_registry);std::cout << st.ToString() << std::endl;return 0;
}
Runtime Error: function Len(f64) not found

這些都可以在編譯期做檢查來避免一些簡單的錯誤。

Short-Circuit Evaluation

athena 優化條件語句實現,僅執行必要分支。舉例:

double LoadF64(void* entry_arguments, int32_t index) {auto* args = reinterpret_cast<double*>(entry_arguments);return args[index];
}void bench_short_path(benchmark::State& state) {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);athena::FunctionSignature sign("load", {athena::ValueType::kPtr, athena::ValueType::kI32}, athena::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign, reinterpret_cast<void*>(LoadF64));std::string code = R"(v1 = load(entry_arg, 0);v2 = load(entry_arg, 1);r = if(v1 + v2 < 100000000, floor(log2(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);athena::RetType ret;std::vector<double> value = {100000000, 100000000};for (auto _ : state) {athena.Execute(value.data(), &ret);}// std::cout << "ret=" << std::get<double>(ret) << '\n';
}void bench_run_all_path(benchmark::State& state) {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);athena::FunctionSignature sign("load", {athena::ValueType::kPtr, athena::ValueType::kI32}, athena::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign, reinterpret_cast<void*>(LoadF64));std::string code = R"(v1 = load(entry_arg, 0);v2 = load(entry_arg, 1);r = max(floor(log2(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);athena::RetType ret;std::vector<double> value = {100000000, 100000000};for (auto _ : state) {athena.Execute(value.data(), &ret);}// std::cout << "ret=" << std::get<double>(ret) << '\n';
}
BENCHMARK(bench_short_path);
BENCHMARK(bench_run_all_path);
BENCHMARK_MAIN();

這段代碼從邏輯上來說不能完全等價, 但我們關注的是 if 語句和 max 函數的區別, if 在 athena 里的實現只會執行其中一個分支, 而 max 需要把所有分支執行完后比較, 從這個case上來說第一個 benchmark 不會走 log 函數,會直接返回 27,第二個 benchmark 則要執行 log 函數,筆者找了一臺執行 log 數學函數比較慢的機器上跑的結果如下:
在這里插入圖片描述

Common Subexpression Elimination

自動識別并合并相同計算路徑。無論是簡單的變量計算,還是符合規則的函數調用,只要計算邏輯相同,athena 均會合并計算。

比如,下面這個例子里,顯然 add1 和 add2 是一樣的。

double LoadF64(void* entry_arguments, int32_t index) {auto* args = reinterpret_cast<double*>(entry_arguments);return args[index];
}int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);athena::FunctionSignature sign("load", {athena::ValueType::kPtr, athena::ValueType::kI32}, athena::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign, reinterpret_cast<void*>(LoadF64));std::string code = R"(v1 = load(entry_arg, 0);v2 = load(entry_arg, 1);add1 = v1 + v2;add2 = v1 + v2;add3 = add1 + add2;)";std::vector<double> value = {100000000, 100000000};auto st = athena.Compile(code, func_registry);std::cout << st.ToString() << '\n';return 0;
}

它編譯出來的中間代碼則只會計算一次 v1 + v2。

; ModuleID = 'module'
source_filename = "module"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"; Function Attrs: nofree nounwind memory(read)
define double @entry(ptr noalias readonly %0, ptr noalias nocapture readnone %1, ptr noalias nocapture readnone %2) local_unnamed_addr #0 {
entryBB:%call_load = tail call double @"load(ptr, i32)"(ptr %0, i32 0)%call_load1 = tail call double @"load(ptr, i32)"(ptr %0, i32 1)%3 = fadd double %call_load, %call_load1%4 = fadd double %3, %3ret double %4
}; Function Attrs: nofree nounwind memory(read)
declare double @"load(ptr, i32)"(ptr, i32) local_unnamed_addr #0attributes #0 = { nofree nounwind memory(read) }

可能你會想知道如果是函數調用,是否可以合并。不考慮直接使用 LLVM API 實現的 intrinic function,只考慮 C 函數的話,在 athena 里遵循一定的規則就可以合并。

athena 推薦用戶將函數分為兩類,一種 read only function,一種是 store function,對應的注冊接口如下:

  // Register ReadOnlyCFuncStatus RegisterReadOnlyCFunc(const FunctionSignature &func_sign, void *c_func_ptr);// Register StoreCFunc// store_args_index is the index of the args in the function signature that is OuputNodeStatus RegisterStoreCFunc(const FunctionSignature &func_sign, void *c_func_ptr, uint32_t store_args_index);

在 athena 里只要函數不直接修改入參的變量,通過生成新的變量返回函數結果,堆內存分配通過 exec_ctx 分配(該行為不被認為是修改入參),則可以被認為是 read only function。把計算結果通過 output 指針寫到用戶定義的區域,以便用戶在引擎執行完后可以獲取到結果,這類函數被認為是 store function。在計算任務里,大體都可以被拆成這兩種函數。假設執行過程中只會有這兩種函數,則 athena 也會合并相同的計算。舉例:

athena::I32ListStruct LoadI32List(void* entry_arguments, int32_t index) {auto* args = reinterpret_cast<std::vector<int32_t>*>(entry_arguments);athena::I32ListStruct result;result.data = args[index].data();result.len = args[index].size();return result;
}int32_t StoreI32List(void* output, int32_t index, athena::I32ListStruct value) {auto store_i = reinterpret_cast<std::vector<int32_t>*>(output)[index];store_i.resize(value.len);std::copy_n(value.data, value.len, store_i.begin());return 0;
}int main() {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);athena::FunctionSignature sign1("load", {athena::ValueType::kPtr, athena::ValueType::kI32},athena::ValueType::kI32List);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void*>(LoadI32List));athena::FunctionSignature sign2("store",{athena::ValueType::kPtr, athena::ValueType::kI32, athena::ValueType::kI32List},athena::ValueType::kI32);func_registry->RegisterStoreCFunc(sign2, reinterpret_cast<void*>(StoreI32List), 1);std::string code = R"(a = load(entry_arg, 0);b = GenLargeBitmap(a, 3, exec_ctx);c = load(entry_arg, 1);r1 = store(output, 0, FilterByBitmap(a, b, CountBits(b), exec_ctx));r2 = store(output, 1, FilterByBitmap(c, b, CountBits(b), exec_ctx));)";auto st = athena.Compile(std::vector<std::string>{code}, func_registry);std::cout << st.ToString() << '\n';return 0;
}

這段代碼從 entry_arg 里加載了兩個 i32list 命名為 a, c,然后生成一個 a > 3 的位圖,根據這個位圖過濾 a,c,得到的結果寫入到 output 里。這段代碼編譯后的中間代碼表示是這樣的。

; ModuleID = 'module'
source_filename = "module"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"%I32ListStruct = type { ptr, i32 }
%U8ListStruct = type { ptr, i32 }; Function Attrs: nounwind memory(read, argmem: readwrite)
define noundef i8 @entry(ptr noalias readonly %0, ptr noalias %1, ptr noalias nocapture %2) local_unnamed_addr #0 {
entryBB:%call_load = tail call %I32ListStruct @"load(ptr, i32)"(ptr %0, i32 0)%call_GenLargeBitmap = tail call %U8ListStruct @"GenLargeBitmap(i32list, i32, ptr)"(%I32ListStruct %call_load, i32 3, ptr %1)%call_CountBits = tail call i32 @"CountBits(u8list)"(%U8ListStruct %call_GenLargeBitmap)%call_FilterByBitmap = tail call %I32ListStruct @"FilterByBitmap(i32list, u8list, u32, ptr)"(%I32ListStruct %call_load, %U8ListStruct %call_GenLargeBitmap, i32 %call_CountBits, ptr %1)%call_store = tail call i32 @"store(ptr, i32, i32list)"(ptr %2, i32 0, %I32ListStruct %call_FilterByBitmap)%call_load4 = tail call %I32ListStruct @"load(ptr, i32)"(ptr %0, i32 1)%call_FilterByBitmap10 = tail call %I32ListStruct @"FilterByBitmap(i32list, u8list, u32, ptr)"(%I32ListStruct %call_load4, %U8ListStruct %call_GenLargeBitmap, i32 %call_CountBits, ptr %1)%call_store11 = tail call i32 @"store(ptr, i32, i32list)"(ptr %2, i32 1, %I32ListStruct %call_FilterByBitmap10)ret i8 0
}; Function Attrs: nofree nounwind memory(read)
declare %I32ListStruct @"load(ptr, i32)"(ptr, i32) local_unnamed_addr #1; Function Attrs: nofree nounwind memory(read)
declare %U8ListStruct @"GenLargeBitmap(i32list, i32, ptr)"(%I32ListStruct, i32, ptr) local_unnamed_addr #1; Function Attrs: nofree nounwind memory(read)
declare i32 @"CountBits(u8list)"(%U8ListStruct) local_unnamed_addr #1; Function Attrs: nofree nounwind memory(read)
declare %I32ListStruct @"FilterByBitmap(i32list, u8list, u32, ptr)"(%I32ListStruct, %U8ListStruct, i32, ptr) local_unnamed_addr #1; Function Attrs: nounwind memory(argmem: readwrite)
declare i32 @"store(ptr, i32, i32list)"(ptr noalias nocapture, i32, %I32ListStruct) local_unnamed_addr #2attributes #0 = { nounwind memory(read, argmem: readwrite) }
attributes #1 = { nofree nounwind memory(read) }
attributes #2 = { nounwind memory(argmem: readwrite) }

GenLargeBitmap 是相同的計算,所以只執行了一次,CountBits 也是相同的計算,也只執行了一次。

Vectorization

在 athena 中,對 list 類型的函數進行了大量優化,使得大部分代碼都能很好地支持自動向量化,并且能夠依賴編譯器來適配多種平臺。然而,對于某些數學函數,例如 log,編譯器在大多數情況下無法實現自動向量化,因此需要依賴向量化數學庫。為了解決多平臺數學庫向量化的問題,athena 引入了 xsimd。同樣的, 我們拿一段代碼舉例:

static std::mt19937_64 rng(std::random_device{}());
static std::uniform_real_distribution<double> dist(0, 1e8);std::vector<double> GenInputs() {std::vector<double> inputs;inputs.reserve(1000);for (int i = 0; i < 1000; ++i) {inputs.emplace_back(dist(rng));}return inputs;
}static std::vector<double> inputs = GenInputs();athena::F64ListStruct Load(void* entry_arguments) {auto* args = reinterpret_cast<std::vector<double>*>(entry_arguments);athena::F64ListStruct result;result.data = args->data();result.len = args->size();return result;
}void bench_cpp_code(benchmark::State& state) {std::vector<double> result;result.resize(inputs.size());for (auto _ : state) {for (int i = 0; i < inputs.size(); i++) {result[i] = std::log(inputs[i]);}}// for (auto v : result) {//   std::cout << v << '\n';// }
}void bench_athena_vectorization(benchmark::State& state) {athena::Athena athena;std::unique_ptr<athena::FunctionRegistry> func_registry;athena::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);athena::FunctionSignature sign1("load", {athena::ValueType::kPtr}, athena::ValueType::kF64List);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void*>(Load));std::string code = R"(r = ListLog(load(entry_arg), exec_ctx);)";auto st = athena.Compile(code, func_registry);athena::RetType ret;athena::ExecContext exec_ctx(4096);for (auto _ : state) {athena.Execute(exec_ctx, &inputs, &ret);}auto result = std::get<std::vector<double>>(ret);// for (auto v : result) {//   std::cout << v << '\n';// }
}
BENCHMARK(bench_cpp_code);
BENCHMARK(bench_athena_vectorization);
BENCHMARK_MAIN();

這里是用的 gcc7 -O2 -ftree-vectorize 編譯的,結果如下:
在這里插入圖片描述

Benchmark

總的來說,athena 進行了許多優化,那么與其他開源執行引擎相比,它的性能如何呢?在這里,筆者選擇了 exprtk 和 gandiva 進行測試。原本也計劃加入 velox,但由于 velox 的依賴庫較多,編譯起來比較麻煩。有興趣的朋友可以自行嘗試進行對比。

我們選取了一個當前業務中使用的表達式進行測試:“if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)”。這個表達式涵蓋了條件語句和數學運算。由于 gandiva 是列存引擎,我們將進行不同批次(batch)的測試。此外,由于 exprtk 僅支持浮點數運算,因此我們在測試中均使用 double 類型。代碼如下:

#include "benchmark/benchmark.h"
#include <chrono>
#include <cstddef>
#include <iostream>
#include <random>
#include "arrow/array/array_base.h"
#include "arrow/array/builder_base.h"
#include "arrow/record_batch.h"
#include "arrow/status.h"
#include "arrow/type_fwd.h"
#include "athena/athena.h"
#include "exec_engine.h"
#include "gandiva/expression.h"
#include "gandiva/gandiva_aliases.h"
#include "gandiva/parser.h"
#include "gandiva/projector.h"
#include "gandiva/tree_expr_builder.h"
#include "riemann/3rd/exprtk/exprtk.hpp"
#include "type.h"namespace {
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_real_distribution<double> eng_f64(0, 1e8);struct TestInput {double v1;double v2;
};constexpr size_t kBatchSize = 100000;
std::vector<TestInput> GenInputs() {std::vector<TestInput> inputs;for (int i = 0; i < kBatchSize; ++i) {TestInput input{.v1 = eng_f64(rng), .v2 = eng_f64(rng)};// std::cout << "v1=" << input.v1 << " v2=" << input.v2 << '\n';inputs.emplace_back(input);}return inputs;
}std::vector<TestInput> inputs = GenInputs();struct TestInputVec {std::vector<double> v1;std::vector<double> v2;
};void bench_exprtk_expr(benchmark::State &state) {typedef exprtk::symbol_table<double> symbol_table_t;typedef exprtk::expression<double> expression_t;typedef exprtk::parser<double> parser_t;typedef exprtk::parser_error::type error_t;std::string expression_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";symbol_table_t symbol_table;symbol_table.add_constants();double s1;double s2;symbol_table.add_variable("v1", s1);symbol_table.add_variable("v2", s2);expression_t expression;expression.register_symbol_table(symbol_table);parser_t parser;parser.compile(expression_str, expression);double ans;const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {s1 = inputs[i].v1;s2 = inputs[i].v2;ans = expression.value();}}// std::cout << ans << '\n';
}double LoadV1(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v1; }double LoadV2(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v2; }void bench_athena(benchmark::State &state) {athena::Athena athena;std::unique_ptr<jitfusion::FunctionRegistry> func_registry;jitfusion::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);jitfusion::FunctionSignature sign1("LoadV1", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void *>(LoadV1));jitfusion::FunctionSignature sign2("LoadV2", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign2, reinterpret_cast<void *>(LoadV2));std::string code = R"(v1 = LoadV1(entry_arg);v2 = LoadV2(entry_arg);r = if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);jitfusion::RetType ret;athena::ExecContext exec_ctx(4096);const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {athena.Execute(exec_ctx, &inputs[i], &ret);}}// std::cout << std::get<double>(ret) << '\n';
}void PrintSimple(const std::vector<std::shared_ptr<arrow::Array>> &arrays) {// std::cout << arrays.size() << std::endl;for (const auto &i : arrays) {const auto &array = std::static_pointer_cast<arrow::DoubleArray>(i);for (int i = 0; i < array->length(); i++) {std::cout << "value " << i << "=" << array->raw_values()[i] << '\n';}}
}void bench_gandiva(benchmark::State &state) {std::string expr_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";// prep gandivaauto field_v1_type = arrow::field("v1", arrow::float64());auto field_v2_type = arrow::field("v2", arrow::float64());auto v1 = gandiva::TreeExprBuilder::MakeField(field_v1_type);auto v2 = gandiva::TreeExprBuilder::MakeField(field_v2_type);auto v1_add_v2 = gandiva::TreeExprBuilder::MakeFunction("add", {v1, v2}, arrow::float64());auto literal_1 = gandiva::TreeExprBuilder::MakeLiteral(1.0);auto v1_add_v2_add_1 = gandiva::TreeExprBuilder::MakeFunction("add", {v1_add_v2, literal_1}, arrow::float64());auto log10_result = gandiva::TreeExprBuilder::MakeFunction("log10", {v1_add_v2_add_1}, arrow::float64());auto floor_result = gandiva::TreeExprBuilder::MakeFunction("floor", {log10_result}, arrow::float64());auto literal_100000000 = gandiva::TreeExprBuilder::MakeLiteral(100000000.0);auto literal_27 = gandiva::TreeExprBuilder::MakeLiteral(27.0);auto cmp = gandiva::TreeExprBuilder::MakeFunction("less_than", {v1_add_v2, literal_100000000}, arrow::boolean());auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, floor_result, literal_27, arrow::float64());// auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, v1_add_v2, literal_27, arrow::float64());auto field_result = arrow::field("result", arrow::float64());auto gandiva_expr = gandiva::TreeExprBuilder::MakeExpression(conditional, field_result);auto schema = arrow::schema({field_v1_type, field_v2_type});// std::cout << "expr: " << gandiva_expr->ToString() << '\n';// std::cout << "schema: " << schema->ToString() << std::endl;// std::cout << "schema metadata: " << schema->ToString(true) << std::endl;std::shared_ptr<gandiva::Projector> projector;auto status = gandiva::Projector::Make(schema, {gandiva_expr}, &projector);if (!status.ok()) {std::cout << status.ToString() << '\n';return;}std::vector<std::shared_ptr<arrow::Array>> input_arr(2);const int batch_size = state.range(0);arrow::DoubleBuilder builder;auto ret = builder.Reserve(batch_size);std::vector<double> v1s;v1s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v1s.emplace_back(inputs[i].v1);}ret = builder.AppendValues(v1s);ret = builder.Finish(input_arr.data());builder.Reset();std::vector<double> v2s;v2s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v2s.emplace_back(inputs[i].v2);}ret = builder.AppendValues(v2s);ret = builder.Finish(&input_arr[1]);auto *pool = arrow::default_memory_pool();// std::cout << pool->backend_name() << std::endl;auto in_batch = arrow::RecordBatch::Make(schema, batch_size, input_arr);arrow::ArrayVector outputs;for (auto _ : state) {projector->Evaluate(*in_batch, pool, &outputs);}// PrintSimple(input_arr);// PrintSimple(outputs);// std::cout << "value =" << std::static_pointer_cast<arrow::DoubleArray>(outputs[0])->raw_values()[batch_size - 1]//           << '\n';
}BENCHMARK(bench_exprtk_expr)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_athena)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_gandiva)->RangeMultiplier(10)->Range(10, kBatchSize);}  // namespaceBENCHMARK_MAIN();

在這次測試中,我們特別優待了 gandiva,沒有將數據從行轉列的重組過程開銷計算在內,因為這個轉換效率因人而異,并且在不同場景中表現也有所不同。以下是這次benchmark 的結果:
在這里插入圖片描述

首先,athena 的性能全面優于 exprtk。隨著批次(batch)規模的增加,gandiva 逐漸超過了 athena,但并沒有拉開太大的差距。正如之前提到的,這里沒有將數據轉換的開銷計算在內,那么如果將其考慮進去,結果會如何呢?

#include "benchmark/benchmark.h"
#include <chrono>
#include <cstddef>
#include <iostream>
#include <random>
#include "arrow/array/array_base.h"
#include "arrow/array/builder_base.h"
#include "arrow/record_batch.h"
#include "arrow/status.h"
#include "arrow/type_fwd.h"
#include "athena/athena.h"
#include "exec_engine.h"
#include "gandiva/expression.h"
#include "gandiva/gandiva_aliases.h"
#include "gandiva/parser.h"
#include "gandiva/projector.h"
#include "gandiva/tree_expr_builder.h"
#include "riemann/3rd/exprtk/exprtk.hpp"
#include "type.h"namespace {
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_real_distribution<double> eng_f64(0, 1e8);struct TestInput {double v1;double v2;
};constexpr size_t kBatchSize = 100000;
std::vector<TestInput> GenInputs() {std::vector<TestInput> inputs;for (int i = 0; i < kBatchSize; ++i) {TestInput input{.v1 = eng_f64(rng), .v2 = eng_f64(rng)};// std::cout << "v1=" << input.v1 << " v2=" << input.v2 << '\n';inputs.emplace_back(input);}return inputs;
}std::vector<TestInput> inputs = GenInputs();struct TestInputVec {std::vector<double> v1;std::vector<double> v2;
};void bench_exprtk_expr(benchmark::State &state) {typedef exprtk::symbol_table<double> symbol_table_t;typedef exprtk::expression<double> expression_t;typedef exprtk::parser<double> parser_t;typedef exprtk::parser_error::type error_t;std::string expression_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";symbol_table_t symbol_table;symbol_table.add_constants();double s1;double s2;symbol_table.add_variable("v1", s1);symbol_table.add_variable("v2", s2);expression_t expression;expression.register_symbol_table(symbol_table);parser_t parser;parser.compile(expression_str, expression);double ans;const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {s1 = inputs[i].v1;s2 = inputs[i].v2;ans = expression.value();}}// std::cout << ans << '\n';
}double LoadV1(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v1; }double LoadV2(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v2; }void bench_athena(benchmark::State &state) {athena::Athena athena;std::unique_ptr<jitfusion::FunctionRegistry> func_registry;jitfusion::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);jitfusion::FunctionSignature sign1("LoadV1", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void *>(LoadV1));jitfusion::FunctionSignature sign2("LoadV2", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign2, reinterpret_cast<void *>(LoadV2));std::string code = R"(v1 = LoadV1(entry_arg);v2 = LoadV2(entry_arg);r = if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);jitfusion::RetType ret;athena::ExecContext exec_ctx(4096);const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {athena.Execute(exec_ctx, &inputs[i], &ret);}}// std::cout << std::get<double>(ret) << '\n';
}void PrintSimple(const std::vector<std::shared_ptr<arrow::Array>> &arrays) {// std::cout << arrays.size() << std::endl;for (const auto &i : arrays) {const auto &array = std::static_pointer_cast<arrow::DoubleArray>(i);for (int i = 0; i < array->length(); i++) {std::cout << "value " << i << "=" << array->raw_values()[i] << '\n';}}
}void bench_gandiva(benchmark::State &state) {std::string expr_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";// prep gandivaauto field_v1_type = arrow::field("v1", arrow::float64());auto field_v2_type = arrow::field("v2", arrow::float64());auto v1 = gandiva::TreeExprBuilder::MakeField(field_v1_type);auto v2 = gandiva::TreeExprBuilder::MakeField(field_v2_type);auto v1_add_v2 = gandiva::TreeExprBuilder::MakeFunction("add", {v1, v2}, arrow::float64());auto literal_1 = gandiva::TreeExprBuilder::MakeLiteral(1.0);auto v1_add_v2_add_1 = gandiva::TreeExprBuilder::MakeFunction("add", {v1_add_v2, literal_1}, arrow::float64());auto log10_result = gandiva::TreeExprBuilder::MakeFunction("log10", {v1_add_v2_add_1}, arrow::float64());auto floor_result = gandiva::TreeExprBuilder::MakeFunction("floor", {log10_result}, arrow::float64());auto literal_100000000 = gandiva::TreeExprBuilder::MakeLiteral(100000000.0);auto literal_27 = gandiva::TreeExprBuilder::MakeLiteral(27.0);auto cmp = gandiva::TreeExprBuilder::MakeFunction("less_than", {v1_add_v2, literal_100000000}, arrow::boolean());auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, floor_result, literal_27, arrow::float64());// auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, v1_add_v2, literal_27, arrow::float64());auto field_result = arrow::field("result", arrow::float64());auto gandiva_expr = gandiva::TreeExprBuilder::MakeExpression(conditional, field_result);auto schema = arrow::schema({field_v1_type, field_v2_type});// std::cout << "expr: " << gandiva_expr->ToString() << '\n';// std::cout << "schema: " << schema->ToString() << std::endl;// std::cout << "schema metadata: " << schema->ToString(true) << std::endl;std::shared_ptr<gandiva::Projector> projector;auto status = gandiva::Projector::Make(schema, {gandiva_expr}, &projector);if (!status.ok()) {std::cout << status.ToString() << '\n';return;}const int batch_size = state.range(0);// std::cout << pool->backend_name() << std::endl;arrow::ArrayVector outputs;for (auto _ : state) {std::vector<std::shared_ptr<arrow::Array>> input_arr(2);const int batch_size = state.range(0);arrow::DoubleBuilder builder;auto ret = builder.Reserve(batch_size);std::vector<double> v1s;v1s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v1s.emplace_back(inputs[i].v1);}ret = builder.AppendValues(v1s);ret = builder.Finish(input_arr.data());builder.Reset();std::vector<double> v2s;v2s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v2s.emplace_back(inputs[i].v2);}ret = builder.AppendValues(v2s);ret = builder.Finish(&input_arr[1]);auto *pool = arrow::default_memory_pool();// std::cout << pool->backend_name() << std::endl;auto in_batch = arrow::RecordBatch::Make(schema, batch_size, input_arr);projector->Evaluate(*in_batch, pool, &outputs);}// PrintSimple(input_arr);// PrintSimple(outputs);// std::cout << "value =" << std::static_pointer_cast<arrow::DoubleArray>(outputs[0])->raw_values()[batch_size - 1]//           << '\n';
}void bench_athena_optimize(benchmark::State &state) {athena::Athena athena;std::unique_ptr<jitfusion::FunctionRegistry> func_registry;jitfusion::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);jitfusion::FunctionSignature sign1("LoadV1", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);jitfusion::FunctionStructure func_struct1 = {jitfusion::FunctionType::kLLVMIntrinicFunc, nullptr, CallLoadV1Function};func_registry->RegisterFunc(sign1, func_struct1);jitfusion::FunctionSignature sign2("LoadV2", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);jitfusion::FunctionStructure func_struct2 = {jitfusion::FunctionType::kLLVMIntrinicFunc, nullptr, CallLoadV2Function};func_registry->RegisterFunc(sign2, func_struct2);std::string code = R"(v1 = LoadV1(entry_arg);v2 = LoadV2(entry_arg);r = if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);jitfusion::RetType ret;const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {athena.Execute(&inputs[i], &ret);}}// std::cout << std::get<double>(ret) << '\n';
}BENCHMARK(bench_exprtk_expr)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_athena)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_gandiva)->RangeMultiplier(10)->Range(10, kBatchSize);}  // namespaceBENCHMARK_MAIN();

在這里插入圖片描述

可以看到,對于這個表達式來說,只有在數據量達到10萬級別時,gandiva 才顯示出優勢。然而,實際上這些數據已經是預先組裝好的,在拷貝過程中有利于 cpu cache,因此開銷并不特別大。如果在實際業務中使用,轉換效率可能會更低一些。考慮到 athena 實際上支持 list 類型的計算,我們再來對比一下使用 athena 的 list 函數計算這個表達式的效果。

#include "benchmark/benchmark.h"
#include <chrono>
#include <cstddef>
#include <iostream>
#include <random>
#include "arrow/array/array_base.h"
#include "arrow/array/builder_base.h"
#include "arrow/record_batch.h"
#include "arrow/status.h"
#include "arrow/type_fwd.h"
#include "athena/athena.h"
#include "exec_engine.h"
#include "gandiva/expression.h"
#include "gandiva/gandiva_aliases.h"
#include "gandiva/parser.h"
#include "gandiva/projector.h"
#include "gandiva/tree_expr_builder.h"
#include "riemann/3rd/exprtk/exprtk.hpp"
#include "type.h"namespace {
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
std::uniform_real_distribution<double> eng_f64(0, 1e8);struct TestInput {double v1;double v2;
};constexpr size_t kBatchSize = 100000;
std::vector<TestInput> GenInputs() {std::vector<TestInput> inputs;for (int i = 0; i < kBatchSize; ++i) {TestInput input{.v1 = eng_f64(rng), .v2 = eng_f64(rng)};// std::cout << "v1=" << input.v1 << " v2=" << input.v2 << '\n';inputs.emplace_back(input);}return inputs;
}std::vector<TestInput> inputs = GenInputs();struct TestInputVec {std::vector<double> v1;std::vector<double> v2;
};void bench_exprtk_expr(benchmark::State &state) {typedef exprtk::symbol_table<double> symbol_table_t;typedef exprtk::expression<double> expression_t;typedef exprtk::parser<double> parser_t;typedef exprtk::parser_error::type error_t;std::string expression_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";symbol_table_t symbol_table;symbol_table.add_constants();double s1;double s2;symbol_table.add_variable("v1", s1);symbol_table.add_variable("v2", s2);expression_t expression;expression.register_symbol_table(symbol_table);parser_t parser;parser.compile(expression_str, expression);double ans;const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {s1 = inputs[i].v1;s2 = inputs[i].v2;ans = expression.value();}}// std::cout << ans << '\n';
}double LoadV1(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v1; }double LoadV2(void *entry_args) { return reinterpret_cast<TestInput *>(entry_args)->v2; }void bench_athena(benchmark::State &state) {athena::Athena athena;std::unique_ptr<jitfusion::FunctionRegistry> func_registry;jitfusion::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);jitfusion::FunctionSignature sign1("LoadV1", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void *>(LoadV1));jitfusion::FunctionSignature sign2("LoadV2", {jitfusion::ValueType::kPtr}, jitfusion::ValueType::kF64);func_registry->RegisterReadOnlyCFunc(sign2, reinterpret_cast<void *>(LoadV2));std::string code = R"(v1 = LoadV1(entry_arg);v2 = LoadV2(entry_arg);r = if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0);)";athena.Compile(code, func_registry);jitfusion::RetType ret;athena::ExecContext exec_ctx(4096);const int batch_size = state.range(0);for (auto _ : state) {for (int i = 0; i < batch_size; i++) {athena.Execute(exec_ctx, &inputs[i], &ret);}}// std::cout << std::get<double>(ret) << '\n';
}void PrintSimple(const std::vector<std::shared_ptr<arrow::Array>> &arrays) {// std::cout << arrays.size() << std::endl;for (const auto &i : arrays) {const auto &array = std::static_pointer_cast<arrow::DoubleArray>(i);for (int i = 0; i < array->length(); i++) {std::cout << "value " << i << "=" << array->raw_values()[i] << '\n';}}
}void bench_gandiva(benchmark::State &state) {std::string expr_str = "if(v1 + v2 < 100000000, floor(log10(1 + v1 + v2)), 27.0)";// prep gandivaauto field_v1_type = arrow::field("v1", arrow::float64());auto field_v2_type = arrow::field("v2", arrow::float64());auto v1 = gandiva::TreeExprBuilder::MakeField(field_v1_type);auto v2 = gandiva::TreeExprBuilder::MakeField(field_v2_type);auto v1_add_v2 = gandiva::TreeExprBuilder::MakeFunction("add", {v1, v2}, arrow::float64());auto literal_1 = gandiva::TreeExprBuilder::MakeLiteral(1.0);auto v1_add_v2_add_1 = gandiva::TreeExprBuilder::MakeFunction("add", {v1_add_v2, literal_1}, arrow::float64());auto log10_result = gandiva::TreeExprBuilder::MakeFunction("log10", {v1_add_v2_add_1}, arrow::float64());auto floor_result = gandiva::TreeExprBuilder::MakeFunction("floor", {log10_result}, arrow::float64());auto literal_100000000 = gandiva::TreeExprBuilder::MakeLiteral(100000000.0);auto literal_27 = gandiva::TreeExprBuilder::MakeLiteral(27.0);auto cmp = gandiva::TreeExprBuilder::MakeFunction("less_than", {v1_add_v2, literal_100000000}, arrow::boolean());auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, floor_result, literal_27, arrow::float64());// auto conditional = gandiva::TreeExprBuilder::MakeIf(cmp, v1_add_v2, literal_27, arrow::float64());auto field_result = arrow::field("result", arrow::float64());auto gandiva_expr = gandiva::TreeExprBuilder::MakeExpression(conditional, field_result);auto schema = arrow::schema({field_v1_type, field_v2_type});// std::cout << "expr: " << gandiva_expr->ToString() << '\n';// std::cout << "schema: " << schema->ToString() << std::endl;// std::cout << "schema metadata: " << schema->ToString(true) << std::endl;std::shared_ptr<gandiva::Projector> projector;auto status = gandiva::Projector::Make(schema, {gandiva_expr}, &projector);if (!status.ok()) {std::cout << status.ToString() << '\n';return;}const int batch_size = state.range(0);// std::cout << pool->backend_name() << std::endl;arrow::ArrayVector outputs;for (auto _ : state) {std::vector<std::shared_ptr<arrow::Array>> input_arr(2);const int batch_size = state.range(0);arrow::DoubleBuilder builder;auto ret = builder.Reserve(batch_size);std::vector<double> v1s;v1s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v1s.emplace_back(inputs[i].v1);}ret = builder.AppendValues(v1s);ret = builder.Finish(input_arr.data());builder.Reset();std::vector<double> v2s;v2s.reserve(batch_size);for (int i = 0; i < batch_size; i++) {v2s.emplace_back(inputs[i].v2);}ret = builder.AppendValues(v2s);ret = builder.Finish(&input_arr[1]);auto *pool = arrow::default_memory_pool();// std::cout << pool->backend_name() << std::endl;auto in_batch = arrow::RecordBatch::Make(schema, batch_size, input_arr);projector->Evaluate(*in_batch, pool, &outputs);}// PrintSimple(input_arr);// PrintSimple(outputs);// std::cout << "value =" << std::static_pointer_cast<arrow::DoubleArray>(outputs[0])->raw_values()[batch_size - 1]//           << '\n';
}jitfusion::F64ListStruct LoadV1List(void *entry_args, void *exec_ctx) {// 考慮到gandiva要組裝一次數據,這里athena就復制一份數據測試比較公平。auto *inputs = reinterpret_cast<TestInputVec *>(entry_args);auto *ctx = reinterpret_cast<jitfusion::ExecContext *>(exec_ctx);jitfusion::F64ListStruct result;result.data = reinterpret_cast<double *>(ctx->arena.Allocate(sizeof(double) * inputs->v1.size()));for (size_t i = 0; i < inputs->v1.size(); i++) {result.data[i] = inputs->v1[i];}result.len = static_cast<uint32_t>(inputs->v1.size());return result;
}jitfusion::F64ListStruct LoadV2List(void *entry_args, void *exec_ctx) {auto *inputs = reinterpret_cast<TestInputVec *>(entry_args);auto *ctx = reinterpret_cast<jitfusion::ExecContext *>(exec_ctx);jitfusion::F64ListStruct result;result.data = reinterpret_cast<double *>(ctx->arena.Allocate(sizeof(double) * inputs->v2.size()));for (size_t i = 0; i < inputs->v2.size(); i++) {result.data[i] = inputs->v2[i];}result.len = static_cast<uint32_t>(inputs->v2.size());return result;
}void bench_athena_vectorization(benchmark::State &state) {athena::Athena athena;std::unique_ptr<jitfusion::FunctionRegistry> func_registry;jitfusion::FunctionRegistryFactory::CreateFunctionRegistry(&func_registry);jitfusion::FunctionSignature sign1("LoadV1", {jitfusion::ValueType::kPtr, jitfusion::ValueType::kPtr},jitfusion::ValueType::kF64List);func_registry->RegisterReadOnlyCFunc(sign1, reinterpret_cast<void *>(LoadV1List));jitfusion::FunctionSignature sign2("LoadV2", {jitfusion::ValueType::kPtr, jitfusion::ValueType::kPtr},jitfusion::ValueType::kF64List);func_registry->RegisterReadOnlyCFunc(sign2, reinterpret_cast<void *>(LoadV2List));std::string code = R"(v1 = LoadV1(entry_arg, exec_ctx);v2 = LoadV2(entry_arg, exec_ctx);v3 = ListAddWithMinSize(v1, v2, exec_ctx);condition = GenLessBitmap(v3, 100000000.0, exec_ctx);r = IfByBitmap(condition, ListFloor(ListLog10(ListAdd(v3, 1.0, exec_ctx), exec_ctx), exec_ctx), 27.0, exec_ctx);)";auto st = athena.Compile(code, func_registry);jitfusion::RetType ret;const int batch_size = state.range(0);TestInputVec input_vec;input_vec.v1.reserve(batch_size);input_vec.v2.reserve(batch_size);for (int i = 0; i < batch_size; i++) {input_vec.v1.emplace_back(inputs[i].v1);input_vec.v2.emplace_back(inputs[i].v2);}jitfusion::ExecContext exec_ctx(static_cast<int64_t>(batch_size * 10 * 8));for (auto _ : state) {athena.Execute(exec_ctx, &input_vec, &ret);}auto result = std::get<std::vector<double>>(ret);// std::cout << result[result.size() - 1] << '\n';
}BENCHMARK(bench_exprtk_expr)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_athena)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_gandiva)->RangeMultiplier(10)->Range(10, kBatchSize);
BENCHMARK(bench_athena_vectorization)->RangeMultiplier(10)->Range(10, kBatchSize);}  // namespaceBENCHMARK_MAIN();

在這里插入圖片描述

對于這個表達式而言,athena 的效率全面超越了 gandiva,提升幅度達到倍數級。然而,athena 并非專注于向量化計算,其支持的數據類型不如 gandiva 底層的 arrow 那樣全面。之所以舉這個例子,是為了說明 athena 在處理 list 類型運算時同樣具備極高的效率。

結語

athena 執行引擎精準定位小 batch、可預編譯的高性能計算場景,通過創新的設計架構、強大的優化策略,在眾多執行引擎中脫穎而出。目前庫已開源:https://github.com/viktorika/jitfusion/tree/main/athena。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82198.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82198.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82198.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

傳奇各種怪物一覽/圖像/爆率/產出/刷新地/刷新時間/刷怪時間

名稱圖像顯示名等級血量攻擊可召喚產出刷新蝙蝠蝙蝠530-22,0,0可誘惑回城卷(1.00%) 金幣(1.00%*500)雞雞551-1,0,0可誘惑雞肉(100.00%)比奇省(29550,62550)5分鐘35只 比奇省(35025,20025)5分鐘25只 比奇省(34025,31025)5分鐘25只 比奇省(40525,24025)5分鐘25只 比奇省(28025,26…

MySQL--day7--聚合函數

&#xff08;以下內容全部來自上述課程&#xff09; 聚合函數 1. 介紹 聚合函數作用于一組數據&#xff0c;并對一組數據返回一個值。 聚合函數類型 AVG&#xff08;&#xff09;SUM&#xff08;&#xff09;MAX&#xff08;&#xff09;MIN&#xff08;&#xff09;COU…

[Java] 封裝

目錄 1. 什么是封裝 2. 訪問修飾符 3. 封裝的好處 4. 封裝的步驟 5. 包 5.1 什么是包 5.2 導入包中的類 5.3 自定義包 5.4 常用的包 6. static關鍵字 6.1 static修飾成員變量 6.2 static修飾成員方法 6.3 Static修飾成員變量初始化 7. 代碼塊 7.1 普通代碼塊 …

Axure元件動作五:設置列表選中項

親愛的小伙伴,在您瀏覽之前,煩請關注一下,在此深表感謝!如有幫助請訂閱專欄! Axure產品經理精品視頻課已登錄CSDN可點擊學習https://edu.csdn.net/course/detail/40420 演示視頻: Axure設置列表選中項 課程主題:設置列表選中項 主要內容:下拉列表選項、值、變量值、焦…

Spring框架--IOC技術

一、Spring框架的介紹 1、Spring框架的概述 Spring 是一個開放源代碼的設計層面框架&#xff0c;它解決的是業務邏輯層和其他各層的松耦合問題&#xff0c;因此它將面向接口的編程思想貫穿整個系統應用。Spring是于2003年興起的一個輕量級的Java開發框架&#xff0c;由 Rod Jo…

Flannel后端為UDP模式下,分析數據包的發送方式——tun設備(三)

在分析 Kubernetes 環境中 Flannel UDP 模式的數據包轉發時&#xff0c;我們提到 flannel.1 是一個 TUN 設備&#xff0c;它在數據包處理中起到了關鍵作用。 什么是 TUN 設備&#xff1f; TUN 設備&#xff08;Tunnel 設備&#xff09;是 Linux 系統中一種虛擬網絡接口&#x…

2025深圳國際無人機展深度解析:看點、廠商與創新亮點

2025深圳國際無人機展深度解析&#xff1a;看點、廠商與創新亮點 1.背景2.核心看點&#xff1a;技術突破與場景創新2.1 eVTOL&#xff08;飛行汽車&#xff09;的規模化展示2.2 智能無人機與無人值守平臺2.3 新材料與核心零部件革新2.4 動態演示與賽事活動 3.頭部無人機廠商4.核…

【Jitsi Meet】(騰訊會議的平替)Docker安裝Jitsi Meet指南-使用內網IP訪問

Docker安裝Jitsi Meet指南-使用內網IP訪問 下載官方代碼配置環境變量復制示例環境文件并修改配置&#xff1a;編輯 .env 文件&#xff1a; 修改 docker-compose.yml 文件生成自簽名證書啟動服務最終驗證 騰訊會議的平替。我們是每天開早晚會的&#xff0c;都是使用騰訊會議。騰…

使用Spring Boot和Spring Security結合JWT實現安全的RESTful API

使用Spring Boot和Spring Security結合JWT實現安全的RESTful API 引言 在現代Web應用中&#xff0c;安全性是至關重要的。Spring Boot和Spring Security提供了強大的工具來保護我們的應用程序&#xff0c;而JWT&#xff08;JSON Web Token&#xff09;則是一種輕量級的認證和…

對神經正切核的理解和推導(1)

聲明&#xff1a; 本文是對Neural Tangent Kernel &#xff08;NTK&#xff09;基礎推導 - Gearlesskai - 博客園文章內容的理解與推導&#xff0c;里面很多東西對我這種新手來說不太好理解&#xff0c;所以我力求通過這種方式理解文章的大部分內容。希望我的理解可以幫助你更…

基于 STC89C52 的養殖場智能溫控系統設計與實現

摘要 本文提出一種基于 STC89C52 單片機的養殖場環境溫度智能控制系統,通過集成高精度溫度傳感器、智能執行機構及人機交互模塊,實現對養殖環境的實時監測與自動調控。系統具備溫度閾值設定、超限報警及多模式控制功能,可有效提升養殖環境穩定性,降低能耗與人工成本。 一…

微信小程序調試

一、尋找答案 1. 創建小程序 https://zhuanlan.zhihu.com/p/1906013675883561860 2. 若有后端接口&#xff0c;需要調試 https://blog.csdn.net/animatecat/article/details/126949749 3. 比較細教程, 搭建修改配置 https://zhuanlan.zhihu.com/p/1893281527112136235 4. 查找…

使用DeepSeek實現數據處理

一、核心能力全景圖 Ctrl+/ 喚醒智能助手,支持以下數據處理場景: ?? 數據清洗與預處理?? 統計分析與可視化?? 機器學習建模?? 大數據性能優化?? 自動化報告生成? 實時流數據處理二、高頻場景實戰(附魔法口令) 場景1:數據清洗自動化(Python示例) 口令: 處…

符合Python風格的對象(使用 __slots__ 類屬性節省空間)

使用__slots__ 類屬性節省空間 默認情況下&#xff0c;Python 在各個實例中名為__dict__ 的字典里存儲實例屬 性。如 3.9.3 節所述&#xff0c;為了使用底層的散列表提升訪問速度&#xff0c;字典會消 耗大量內存。如果要處理數百萬個屬性不多的實例&#xff0c;通過__slots__…

民宿管理系統5

管理員管理&#xff1a; 新增管理員信息&#xff1a; 前端效果&#xff1a; 前端代碼&#xff1a; <body> <div class"layui-fluid"><div class"layui-row"><div class"layui-form"><div class"layui-form-i…

?騰訊地圖軌跡云:重構位置管理的數字神經中樞

——從軌跡追蹤到智能決策&#xff0c;開啟產業互聯網新篇章 在數字經濟與實體經濟深度融合的今天&#xff0c;位置服務已成為企業數字化轉型的核心基礎設施。無論是物流運輸中的車輛調度、共享經濟中的設備管理&#xff0c;還是智慧城市中的交通優化&#xff0c;精準的軌跡數…

rce命令執行原理及靶場實戰(詳細)

2. 原理 在根源上應用系統從設計上要給用戶提供一個指定的遠程命令操作的接口。漏洞主要出現在常見的路由器、防火墻、入侵檢測等設備的web管理界面上。在管理界面提供了一個ping服務。提交后&#xff0c;系統對該IP進行ping&#xff0c;并且返回結果。如果后臺服務器并沒有對…

GeoTools 將 Shp 導入PostGIS 空間數據庫

前言 ? GeoTools 在空間數據轉換處理方面具有強大的能力&#xff0c;能夠高效、簡潔的操縱 Shp 數據。特別是與空間數據庫PostGIS 相結合&#xff0c;更能展示出其空間數據處理的優勢&#xff0c;借助 GeoTools&#xff0c;我們可以實現 Shp 數據高效入庫。 本文上接系列文章 …

基于SpringBoot+Vue的家政服務系統源碼適配H5小程序APP

市場前景 隨著社會經濟的發展和人口老齡化的加劇&#xff0c;家政服務需求不斷增長。我國65歲及以上人口增長較快&#xff0c;2022年我國65歲及以上老年人數量達2.1億人&#xff0c;占比較2016年增長4.1個百分點&#xff0c;達14.9%。我國65歲及以上人口數量龐大&#xff0c;老…

《企業級日志該怎么打?Java日志規范、分層設計與埋點實踐》

大家好呀&#xff01;&#x1f44b; 今天我們要聊一個Java開發中超級重要但又經常被忽視的話題——日志系統&#xff01;&#x1f4dd; 不管你是剛入門的小白&#xff0c;還是工作多年的老司機&#xff0c;日志都是我們每天都要打交道的"好朋友"。那么&#xff0c;如…