ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Apache Arrow Gandiva 表达式、Projector 与 Filter 使用指南(C++)

2026/9/14 17:29:25 拓冰建站 浏览量
Apache Arrow Gandiva 表达式、Projector 与 Filter 使用指南(C++) Apache Arrow Gandiva 表达式、Projector 与 Filter 使用指南C【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow导读本文围绕 Apache Arrow C 子模块 Gandiva 的表达式Expression、投影器Projector与过滤器Filter展开介绍如何用TreeExprBuilder构建表达式树、将其包装为Expression或Condition再通过Projector::Make/Filter::Make编译出可执行内核并批量求值。读完本文你将掌握 Gandiva 表达式构建的完整 API 组合、SelectionVector 位宽选型原则以及“先过滤再投影”这一高性能组合用法并能在 cpp/examples/arrow/gandiva_example.cc 中找到可直接运行验证的完整示例。Gandiva 是 Apache Arrow 中基于 LLVM 的表达式求值引擎表达式在构建后被编译为机器码执行从而以接近原生代码的速度在 Arrow 列式数据上完成投影与过滤。本文严格依据 docs/source/cpp/gandiva/expr_projector_filter.rst 编写并结合cpp/src/gandiva目录下的头文件与示例源码进行补充。构建表达式TreeExprBuilder 与表达式树Gandiva 提供了一套通用的表达式表示表达式由一棵节点树tree of nodes构成所有树的构建都通过TreeExprBuilder完成。表达式树的叶子节点通常是两类字段引用由TreeExprBuilder::MakeField创建字面量由TreeExprBuilder::MakeLiteral以及MakeStringLiteral、MakeBinaryLiteral、MakeDecimalLiteral、MakeNull创建。叶子节点可以被组合成更复杂的表达式树官方文档给出了四类核心组合器组合器作用TreeExprBuilder::MakeFunction创建函数节点可调用GetRegisteredFunctionSignatures获取合法的函数签名列表TreeExprBuilder::MakeIf创建 if-else 分支逻辑TreeExprBuilder::MakeAnd/MakeOr创建布尔表达式取“非”请使用MakeFunction中的not(bool)函数TreeExprBuilder::MakeInExpressionInt32等创建集合成员测试in 表达式在 tree_expr_builder.h 中可以看到这些 API 的完整声明。其中MakeFunction的原型为static NodePtr MakeFunction(const std::string name, const NodeVector params, DataTypePtr return_type);即需要显式给出函数名如add、less_than、参数节点列表以及返回类型。MakeIf则接收condition、then_node、else_node和result_type四个参数构成典型的三目分支。值得留意的是MakeInExpression*在源码中被细分为多种重载覆盖不同数据类型MakeInExpressionInt32/Int64、Decimal、String、Binary、Float、Double、Date32/Date64、Time32/Time64 与 TimeStamp每种都接收一个节点和一个std::unordered_set常量集合。这意味着集合成员测试同样具备类型安全的重载选择。每个组合器都会创建新的复合节点复合节点以叶子节点或其他复合节点作为子节点通过不断组合可以构建任意复杂度的表达式树。树构建完成后根据用途不同会被包装成两种对象Expression用于投影projectionCondition用于过滤filter。下面是从 gandiva_example.cc 中截取的官方示例——创建表示x 3的Expression与表示x 3的Conditionstd::shared_ptrarrow::Field field_x_raw arrow::field(x, arrow::int32()); std::shared_ptrNode field_x TreeExprBuilder::MakeField(field_x_raw); std::shared_ptrNode literal_3 TreeExprBuilder::MakeLiteral(3); std::shared_ptrarrow::Field field_result arrow::field(result, arrow::int32()); std::shared_ptrNode add_node TreeExprBuilder::MakeFunction(add, {field_x, literal_3}, arrow::int32()); std::shared_ptrExpression expression TreeExprBuilder::MakeExpression(add_node, field_result); std::shared_ptrNode less_than_node TreeExprBuilder::MakeFunction(less_than, {field_x, literal_3}, arrow::boolean()); std::shared_ptrCondition condition TreeExprBuilder::MakeCondition(less_than_node);注意这里的几个细节MakeLiteral(3)通过 C 重载推断为int32_t字面量MakeFunction(add, ...)返回类型显式指定为arrow::int32()条件树的根节点则通过MakeCondition包装为Condition。MakeExpression(root_node, result_field)需要同时给出结果字段用于声明投影输出的列名与类型。函数签名查询GetRegisteredFunctionSignatures文档特别指出创建函数节点前可以调用GetRegisteredFunctionSignatures获取合法函数签名列表从而确认某个函数名、参数类型与返回类型组合是否受支持。在 Gandiva 源码中函数注册表由 function_registry.cc 及其按领域划分的注册文件如 function_registry_arithmetic.cc、function_registry_string.cc、function_registry_datetime.cc 等构建涵盖算术、字符串、日期时间、哈希、数学函数、时间戳运算等类别。FunctionSignature类型及注册表查询接口定义在 function_signature.h 与 function_registry.h 中。对于开发者而言最稳妥的实践是在写死某个函数名之前先枚举签名表核对名称与类型组合避免在Projector::Make/Filter::Make阶段才暴露签名不匹配的错误。Projector 与 Filter两个执行内核Gandiva 提供两个执行内核Projector消费一个 record batch将其投影project为新的 record batchFilter消费一个 record batch产出**SelectionVector**——一个包含所有满足条件行索引的向量。两者在创建实例时都会完成表达式 IR 的优化并且针对静态 schema 编译因此此时必须已知 record batch 的 schema。这一约束在源码中同样体现Projector::Make与Filter::Make的第一个参数都是SchemaPtr schema见 projector.h 与 filter.h。延续上一节的expression与condition官方示例创建 Projector 与 Filter 的代码如下gandiva_example.ccstd::shared_ptrarrow::Schema input_schema arrow::schema({field_x_raw}); std::shared_ptrarrow::Schema output_schema arrow::schema({field_result}); std::shared_ptrProjector projector; Status status; std::vectorstd::shared_ptrExpression expressions {expression}; status Projector::Make(input_schema, expressions, projector); ARROW_RETURN_NOT_OK(status); std::shared_ptrFilter filter; status Filter::Make(input_schema, condition, filter); ARROW_RETURN_NOT_OK(status);Projector::Make接收表达式向量可一次编译多个投影表达式Filter::Make接收单个Condition。此外Projector还提供两个可选Make重载分别用于注入运行时Configuration以及指定SelectionVector::Mode详见后文“过滤 投影组合使用”一节Filter::Make同样支持自定义Configuration。一旦 Projector 或 Filter 创建完成即可对 Arrow record batch 反复求值。这两个执行内核自身是单线程的但被设计为可复用、并行处理不同的 record batch——即把同一个 Projector/Filter 实例分发给多个线程各自处理不同的批次从而获得并行吞吐。求值投影Projector::Evaluate执行通过Projector::Evaluate完成它输出一个数组向量arrow::ArrayVector将该向量连同输出 schema 一起传给arrow::RecordBatch::Make()即可组装出结果批次。源码中Evaluate有多个重载见 projector.h无选择向量版本Evaluate(batch, pool, output)输出数组从内存池pool分配无选择向量、调用者自备输出数组版本Evaluate(batch, output)带选择向量版本Evaluate(batch, selection_vector, pool, output)与Evaluate(batch, selection_vector, output)后者要求调用者预先分配具有足够容量的数组。官方示例gandiva_example.cc展示了完整的投影求值流程auto pool arrow::default_memory_pool(); int num_records 4; arrow::Int32Builder builder; int32_t values[4] {1, 2, 3, 4}; ARROW_RETURN_NOT_OK(builder.AppendValues(values, 4)); ARROW_ASSIGN_OR_RAISE(std::shared_ptrarrow::Array array, builder.Finish()); auto in_batch arrow::RecordBatch::Make(input_schema, num_records, {array}); arrow::ArrayVector outputs; status projector-Evaluate(*in_batch, pool, outputs); ARROW_RETURN_NOT_OK(status); std::shared_ptrarrow::RecordBatch result arrow::RecordBatch::Make(output_schema, outputs[0]-length(), outputs);这里输入批次in_batch的 schema 必须与Projector::Make时传入的 schema 一致输出数组由Evaluate从pool中分配因此调用方无需预分配内存。求值过滤Filter::Evaluate 与 SelectionVectorFilter::Evaluate产出SelectionVector——一个匹配过滤条件的行索引向量。从源码看SelectionVector 本质上是Arrow 整数数组的包装器按位宽参数化selection_vector.h其Mode枚举包括MODE_NONE、MODE_UINT16、MODE_UINT32、MODE_UINT64并提供MakeInt16/MakeInt32等工厂方法MakeInt16(max_slots, pool, selection_vector)为最常见用法。SelectionVector 必须在传给Evaluate()之前初始化初始化时需要确定两个关键参数位宽bitwidth决定它能容纳的最大索引值最大槽位数max slots决定它能包含多少条索引。文档给出的选型原则非常明确一般情况下max slots 应设置为批次大小batch size位宽应选择能表示所有小于批次大小的整数的最小整数位宽。例如批次大小为 100k 时max slots 设为 100k位宽选 32 位因为 2^16 64k不足以表示 100k 以内的索引。这一点在示例代码中也有印证gandiva_example.cc4 行数据的批次使用MakeInt16并将max_slots设为in_batch-num_rows()。Evaluate()运行完毕后SelectionVector 被填充此时用SelectionVector::ToArray()取出底层数组再交给arrow::compute::Take()物化出最终输出批次std::shared_ptrgandiva::SelectionVector result_indices; // Use 16-bit integers for indices. Result can be no longer than input size, // so use batch num_rows as max_slots. status gandiva::SelectionVector::MakeInt16(/*max_slots*/in_batch-num_rows(), pool, result_indices); ARROW_RETURN_NOT_OK(status); status filter-Evaluate(*in_batch, result_indices); ARROW_RETURN_NOT_OK(status); std::shared_ptrarrow::Array take_indices result_indices-ToArray(); Datum maybe_batch; ARROW_ASSIGN_OR_RAISE(maybe_batch, arrow::compute::Take(Datum(in_batch), Datum(take_indices), TakeOptions::NoBoundsCheck())); result maybe_batch.record_batch();由于Take使用TakeOptions::NoBoundsCheck()此处可以避免多余的越界检查开销依赖Take的接口位于 arrow/compute/api_vector.h示例文件第 19 行#include。过滤 投影组合使用最后Gandiva 还支持在投影的同时应用选择向量即带过滤的投影。关键前提有两个初始化 Projector 时传入SelectionVector::GetMode()使投影器按正确的位宽编译求值时把 SelectionVector 传入Projector::Evaluate()的带选择向量重载。Projector::Make的对应重载为Make(schema, exprs, selection_vector_mode, configuration, projector)见 projector.h。官方示例gandiva_example.cc// Make sure the projector is compiled for the appropriate selection vector mode status Projector::Make(input_schema, expressions, result_indices-GetMode(), ConfigurationBuilder::DefaultConfiguration(), projector); ARROW_RETURN_NOT_OK(status); arrow::ArrayVector outputs_filtered; status projector-Evaluate(*in_batch, result_indices.get(), pool, outputs_filtered); ARROW_RETURN_NOT_OK(status); result arrow::RecordBatch::Make(output_schema, outputs[0]-length(), outputs_filtered);注意此处显式传入ConfigurationBuilder::DefaultConfiguration()示例第 33 行using gandiva::ConfigurationBuilder;其完整定义位于 configuration.cc。该组合的完整运行结果依次打印三段输出Project result:、Filter result:与Project filter result:读者可自行编译 gandiva_example.cc 验证输入x ∈ {1, 2, 3, 4}投影x 3得到{4, 5, 6, 7}过滤x 3选出索引{0, 1}经Take物化后得到{1, 2}过滤 投影组合则直接对选中行计算x 3得到{4, 5}。实践要点小结schema 先行Projector/Filter 均在创建时针对静态 schema 编译projector.h、filter.h因此输入批次的 schema 必须与Make时一致实例复用执行内核单线程但同一实例可在多个线程上并行处理不同批次SelectionVector 提前初始化位宽与 max slots 需在Evaluate前确定位宽不足会限制可表示的最大索引组合求值位宽匹配带选择向量的投影必须用SelectionVector::GetMode()编译 Projector否则位宽不匹配完整示例所有可运行代码集中在 gandiva_example.cc配套构建入口见 cpp/examples/arrow/CMakeLists.txtGandiva 的 C API 绑定与测试分别位于 c_glib/gandiva-glib 与 c_glib/test/gandiva可作为跨语言调用与行为验证的参考。【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考