apache/arrow
Gandiva
Active contributors: Sutou Kouhei, Dmitry Chirkov, Logan Riggs, Antoine Pitrou
Gandiva is an LLVM-based JIT compiler for Arrow expressions. It lives in cpp/src/gandiva/ and was contributed by Dremio in May 2018. Given a tree of Arrow Expressions, Gandiva produces native machine code that filters or projects record batches at runtime — typically much faster than interpreting expressions kernel-by-kernel.
Purpose
Compile vectorized expressions to native code. The classical way to evaluate (a + b) * c > 10 is to call three compute kernels in sequence, materializing intermediate columns. Gandiva fuses that whole expression tree into a single function that does one pass over the record batch.
Two main entry points
| Class | File | Use |
|---|---|---|
gandiva::Projector |
projector.h |
Compiles a list of expressions; given a record batch, produces output arrays. |
gandiva::Filter |
filter.h |
Compiles a single boolean expression; given a record batch, produces a selection vector. |
A typical session:
auto schema = arrow::schema({field("a", int32()), field("b", int32())});
auto a = TreeExprBuilder::MakeField(schema->field(0));
auto b = TreeExprBuilder::MakeField(schema->field(1));
auto sum = TreeExprBuilder::MakeFunction("add", {a, b}, int32());
auto expr = TreeExprBuilder::MakeExpression(sum, field("sum", int32()));
std::shared_ptr<gandiva::Projector> projector;
ARROW_RETURN_NOT_OK(gandiva::Projector::Make(schema, {expr}, &projector));
arrow::ArrayVector outputs;
ARROW_RETURN_NOT_OK(projector->Evaluate(*batch, pool, &outputs));Pipeline
graph LR
Tree["TreeExprBuilder builds Node tree"]
Tree --> Decompose["ExprDecomposer: simplify + lift validity"]
Decompose --> DEX["DEX: typed intermediate representation"]
DEX --> LLVM["LLVM IR generation (llvm_generator.cc)"]
LLVM --> Bitcode["Link with precompiled bitcode (math, regex, decimal)"]
Bitcode --> Optimizer["LLVM optimizer passes"]
Optimizer --> JIT["JIT compile to native code"]
JIT --> Cache["Cache by ExpressionKey"]
Cache --> Run["Run over record batches"]Files
| File | Purpose |
|---|---|
node.h, tree_expr_builder.{h,cc} |
The expression tree and its builder. |
expr_decomposer.{h,cc} |
Lowers an expression to a DEX (decomposed expression) IR that exposes validity propagation explicitly. |
dex.h, dex_visitor.h |
The DEX nodes (literal, field reference, function, if-then-else, ...). |
llvm_generator.{h,cc} (~58 KB), llvm_includes.h, llvm_types.{h,cc} |
LLVM IR emission. The core of Gandiva. |
engine.{h,cc} |
LLVM ORC JIT setup. Loads the precompiled bitcode and optimizes/compiles modules. |
precompiled/ |
C++ helpers compiled to LLVM bitcode at build time. Includes regex, decimal arithmetic, hashing, casting, datetime handling. |
precompiled_bitcode.cc.in |
Template that wraps the bitcode bytes into a C++ symbol the JIT can load. |
make_precompiled_bitcode.py, GandivaAddBitcode.cmake |
Build-time codegen scripts. |
function_registry*.{h,cc} |
The registry of supported functions, split by category (arithmetic, string, datetime, hash, math_ops, timestamp_arithmetic). |
function_holder.h + *_holder.{h,cc} |
Per-function state (e.g., compiled regex objects). |
gdv_function_stubs*.{cc,h} (~113 KB total) |
C++ stubs that DEX calls jump to (regex match, decimal divide, etc.). These are also exposed to the JIT. |
selection_vector.{h,cc} |
Output of Filter — a list of row indices. |
cache.{h,cc}, lru_cache.h |
LRU cache keyed on ExpressionCacheKey so repeated expressions don't re-JIT. |
projector.{h,cc}, filter.{h,cc} |
The two public entry points. |
expression.h, condition.h |
The expression and filter wrappers users build through TreeExprBuilder. |
expression_registry.{h,cc} |
Lists the supported types/functions for client introspection. |
interval_holder.{h,cc}, to_date_holder.{h,cc}, random_generator_holder.{h,cc} |
Per-call state for stateful functions. |
Precompiled bitcode
Gandiva ships a non-trivial chunk of code as precompiled LLVM bitcode rather than emitting it from scratch each time. This includes:
- Decimal128 / Decimal256 arithmetic (
cpp/src/gandiva/decimal_ir.cc,decimal_xlarge.cc). - Regex matching (uses
re2). - Hash functions for ints, doubles, strings.
- Date/time conversions.
- Casts that aren't trivially expressible in LLVM IR (e.g., string ↔ decimal).
The build pipeline:
- C++ source in
cpp/src/gandiva/precompiled/is compiled byclangto LLVM bitcode (irhelpers.bc). make_precompiled_bitcode.pywraps the bitcode bytes into a C++ source file.- The resulting object is linked into
libgandiva.so. - At runtime, the JIT loads the bitcode module and merges it with the user-generated module before optimization.
This is why Gandiva builds need clang available even when the rest of Arrow is built with GCC.
Function registry
The Gandiva function registry is separate from the Arrow compute function registry. They cover overlapping but not identical sets of functions, with Gandiva's set focused on what's worth JIT-compiling. Categories:
function_registry_arithmetic.cc—add,subtract,multiply,divide,mod,power.function_registry_string.cc(~30 KB) —substring,concat,lower,upper,regexp_matches,regexp_replace,split_part, ...function_registry_datetime.cc—date_add,date_diff,extract_year,to_date.function_registry_hash.cc—hash,hash32,hash64.function_registry_math_ops.cc—floor,ceil,round,sqrt,log, etc.function_registry_timestamp_arithmetic.cc— timestamp arithmetic.
Each registry file calls RegisterFunction with a NativeFunction describing the input/output types and the symbol to call.
Caching
Compiled projectors and filters are cached by ExpressionCacheKey (a hash over the expression tree + schema). The cache is bounded by lru_cache.h. Re-evaluating the same expression on a new batch is essentially free of compilation cost.
Test infrastructure
cpp/src/gandiva/tests/ contains projector_test.cc (38 KB), filter_test.cc, and end-to-end tests covering each function category. 58 KB) tests every C++ stub.cpp/src/gandiva/gdv_function_stubs_test.cc (
Language wrapper integration
- PyArrow exposes Gandiva via
python/pyarrow/gandiva.pyx. Public API inpyarrow.gandiva. - R does not expose Gandiva directly (it uses Acero / compute kernels instead).
- C-GLib + Ruby ship
gandiva-glibandred-gandiva.
Performance niches
Gandiva tends to outperform the compute kernel chain when:
- An expression has many operations and the kernel chain would materialize many intermediate columns.
- The data has high null ratios (Gandiva's lifted validity propagation skips work).
- Type combinations are unusual enough that a single fused codepath is much tighter than the kernel registry path.
For simple single-kernel calls, the compute layer is usually faster because no JIT compilation cost is amortized.
Entry points for modification
- Adding a new function: register it in one of the
function_registry_*.ccfiles; provide either a precompiled-bitcode helper (inprecompiled/) or agdv_function_stubs.ccC++ stub. - Improving codegen:
llvm_generator.ccis where most LLVM IR is emitted.engine.cccontrols the optimizer pass pipeline. - Adding a Holder: see existing
regex_functions_holder.cc,interval_holder.cc,to_date_holder.cc.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.