{"id":17473668,"url":"https://github.com/yinqiwen/rapidudf","last_synced_at":"2025-08-20T10:32:41.517Z","repository":{"id":254639334,"uuid":"837427821","full_name":"yinqiwen/rapidudf","owner":"yinqiwen","description":"A High-Performance JIT-Based C++ Expression/Script Execution Engine with SIMD Vectorization Support","archived":false,"fork":false,"pushed_at":"2024-10-21T09:42:21.000Z","size":1003,"stargazers_count":53,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-21T13:53:34.741Z","etag":null,"topics":["cpp17","expression","high-performance","jit","llvm","llvm-jit","math-expressions","script","simd","udf","vectorized"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yinqiwen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-03T00:52:45.000Z","updated_at":"2024-10-21T13:35:10.000Z","dependencies_parsed_at":"2024-10-21T13:19:52.592Z","dependency_job_id":null,"html_url":"https://github.com/yinqiwen/rapidudf","commit_stats":null,"previous_names":["yinqiwen/rapidudf"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yinqiwen%2Frapidudf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yinqiwen%2Frapidudf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yinqiwen%2Frapidudf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yinqiwen%2Frapidudf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yinqiwen","download_url":"https://codeload.github.com/yinqiwen/rapidudf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230415318,"owners_count":18222158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp17","expression","high-performance","jit","llvm","llvm-jit","math-expressions","script","simd","udf","vectorized"],"created_at":"2024-10-18T18:06:14.783Z","updated_at":"2024-12-19T10:10:03.700Z","avatar_url":"https://github.com/yinqiwen.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eRapidUDF\u003c/h1\u003e\n\n`RapidUDF` is a high-performance SIMD vectorized expression/script computation execution engine library designed for online systems. It can be used in scenarios requiring high performance and flexibility such as rule engines, storage systems, and feature computation.\n\n\n## Limitations\n\n- C++17\n\n## Features\n- **Easy to Use**:\n  - Provides support for conventional expression syntax\n  - For more complex logic, supports a C-like DSL including if-elif-else* conditional control, while loop control, auto temporary variables, etc.;\n  - For columnar memory data (vector\u003cT\u003e), provides dynamic Table APIs similar to Spark's DataFrame and operations like filter/order_by/topk/take;\n- **High Performance**:\n  - Based on LLVM JIT compilation, startup and execution performance comparable to native cpp implementation;\n  - For columnar memory data (vector\u003cT\u003e), provides SIMD vectorization acceleration implementation\n- **Thread Safe**: \n  - State-less JIT-generated C methods are naturally thread-safe\n- **FFI**:\n  - Supports zero-cost access to C++ defined class objects (custom classes/stl/protobufs/flatbuffers/...) in expressions/UDFs\n  - Supports zero-cost calls to methods/class methods defined in C++ within expressions/UDFs\n- **Rich Built-in Data Types, Operators, and Functions**:\n  - [built-in data types](docs/dtype.md)\n  - [built-in operators](docs/operator.md)\n  - [built-in functions](docs/builtin_function.md)\n\n## Compilation and Installation\nCompilation requires a compiler that supports C++17\n### Bazel\nAdd in WORKSPACE:\n```python\n    git_repository(\n        name = \"rapidudf\",\n        remote = \"https://github.com/yinqiwen/rapidudf.git\",\n        commit = \"...\",\n    )\n    load(\"@rapidudf//:rapidudf.bzl\", \"rapidudf_workspace\")\n    rapidudf_workspace()\n```\nAdd in the BUILD file for relevant code compilation rules:\n```python\ncc_library(\n    name = \"mylib\",\n    srcs = [\"mylib.cc\"],\n    hdrs = [\n        \"mylib.h\",\n    ],\n    deps = [\n        \"@rapidudf\",\n    ],\n)\n```\n### CMake\nFirst, compile and instal `rapidudf` \n```bash\ncd \u003crapidudf src dir\u003e\nmkdir build; cd build;\ncmake ..\nmake install\n```\nAdd the following to the CMake configuration of the related project:\n```cmake\nfind_package(rapidudf REQUIRED)\n....\n# link rapidudf\ntarget_link_libraries(mylib PRIVATE rapidudf::rapidudf)\n```\n[Example](rapidudf/examples/CMakeLists.txt)\n\n\n## Usage Overview\n\n### Simple Expression\n```cpp\n#include \"rapidudf/rapidudf.h\"\n\nint main() {\n  // 1. If needed, set up rapidudf logger\n  //   std::shared_ptr\u003cspdlog::logger\u003e mylogger;\n  //   rapidudf::set_default_logger(mylogger);\n  // 2. Expression string\n  std::string expression = \"x \u003e= 1 \u0026\u0026 y \u003c 10\";\n  // 3. Compile to generate Function, the generated Function object can be saved for subsequent repeated execution; compilation usually takes between 10ms-100ms;\n  rapidudf::JitCompiler compiler;\n  // CompileExpression's template parameters support multiple types, the first template parameter is the return type, the rest are function parameter types;\n  // Variable names used in the expression need to be passed in as a parameter name list, otherwise compilation fails\n  auto result = compiler.CompileExpression\u003cbool, int, int\u003e(expression, {\"x\", \"y\"});\n  if (!result.ok()) {\n    RUDF_ERROR(\"{}\", result.status().ToString());\n    return -1;\n  }\n  // 4. Execute function\n  rapidudf::JitFunction\u003cbool, int, int\u003e f = std::move(result.value());\n  bool v = f(2, 3);  // true\n  v = f(0, 1);       // false\n  return 0;\n};\n```\n\n### Simple UDF Script\nFibonacci function\n```cpp\n#include \"rapidudf/rapidudf.h\"\n\nint main() {\n  // 1. If needed, can set up rapidudf logger\n  //   std::shared_ptr\u003cspdlog::logger\u003e mylogger;\n  //   rapidudf::set_default_logger(mylogger);\n  // 2. UDF string\n  std::string source = R\"(\n    int fib(int n) \n    { \n       if (n \u003c= 1){\n         return n; \n       }\n       // Supports cpp // comments\n       return fib(n - 1) + fib(n - 2);  // Recursive call\n    } \n  )\";\n  // 3. Compile to generate Function, the generated Function object can be saved for subsequent repeated execution; compilation usually takes between 10ms-100ms;\n  rapidudf::JitCompiler compiler;\n  // CompileFunction's template parameters support multiple types, the first template parameter is the return type, the rest are function parameter types\n  auto result = compiler.CompileFunction\u003cint, int\u003e(source);\n  if (!result.ok()) {\n    RUDF_ERROR(\"{}\", result.status().ToString());\n    return -1;\n  }\n\n  // 4. Execute function\n  rapidudf::JitFunction\u003cint, int\u003e f = std::move(result.value());\n  int n = 9;\n  int x = f(n);  // 34\n  RUDF_INFO(\"fib({}):{}\", n, x);\n  return 0;\n};\n```\n\n### Vector Calculation\n```cpp\n#include \"rapidudf/rapidudf.h\"\n\nusing namespace rapidudf;\nint main() {\n  // 2. UDF string\n  std::string source = R\"(\n    simd_vector\u003cf32\u003e boost_scores(Context ctx, simd_vector\u003cstring_view\u003e location, simd_vector\u003cf32\u003e score) \n    { \n      auto boost=(location==\"home\"?2.0_f32:0_f32);\n      return score*boost;\n    } \n  )\";\n\n  // 3. Compile to generate Function, the generated Function object can be saved for subsequent use\n  rapidudf::JitCompiler compiler;\n  // CompileFunction's template parameters support multiple types, the first template parameter is the return type, the rest are function parameter types\n  // 'rapidudf::Context' is a mandatory parameter involved in arena memory allocation in the simd implementation\n  auto result =\n      compiler.CompileFunction\u003csimd::Vector\u003cfloat\u003e, rapidudf::Context\u0026, simd::Vector\u003cStringView\u003e, simd::Vector\u003cfloat\u003e\u003e(\n          source);\n  if (!result.ok()) {\n    RUDF_ERROR(\"{}\", result.status().ToString());\n    return -1;\n  }\n\n  // 4.1 Test data, need to convert raw data into columnar data\n  std::vector\u003cfloat\u003e scores;\n  std::vector\u003cstd::string\u003e locations;\n  for (size_t i = 0; i \u003c 4096; i++) {\n    scores.emplace_back(1.1 + i);\n    locations.emplace_back(i % 3 == 0 ? \"home\" : \"other\");\n  }\n\n  // 5. Execute function\n  rapidudf::Context ctx;\n  auto f = std::move(result.value());\n  auto new_scores = f(ctx, ctx.NewSimdVector(locations), ctx.NewSimdVector(scores));\n  for (size_t i = 0; i \u003c new_scores.Size(); i++) {\n    // RUDF_INFO(\"{}\", new_scores[i]);\n  }\n  return 0;\n};\n```\n\n### Dynamic Vector Table\n**RapidUDF** supports dynamically creating vector tables, allowing arbitrary computational operations on table columns (accelerated through SIMD) in expressions/UDFs;\nThe table class also provides operations similar to Spark DataFrame, such as:\n- `.filter(simd::Vector\u003cBit\u003e)`   returns a new table instance filtered by condition\n- `.order_by(simd::Vector\u003cT\u003e column, bool descending)`   returns a new table instance sorted by condition\n- `.topk(simd::Vector\u003cT\u003e column, uint32_t k, bool descending)`  returns a new table instance with top k entries\n```cpp\n#include \"rapidudf/rapidudf.h\"\n\nusing namespace rapidudf;\nstruct Student {\n  std::string name;\n  uint16_t age = 0;\n  float score = 0;\n  bool gender = false;\n};\nRUDF_STRUCT_FIELDS(Student, name, age, score, gender)\nint main() {\n  // 1. Create table schema\n  auto schema =\n      simd::TableSchema::GetOrCreate(\"Student\", [](simd::TableSchema* s) { std::ignore = s-\u003eAddColumns\u003cStudent\u003e(); });\n\n  // 2. UDF string, table\u003cTABLE_NAME\u003e generic format where TABLE_NAME must match the previously created table schema name\n  // table supports filter/order_by/topk/take, etc. operations\n  std::string source = R\"(\n    table\u003cStudent\u003e select_students(Context ctx, table\u003cStudent\u003e x) \n    { \n       auto filtered = x.filter(x.score \u003e90 \u0026\u0026 x.age\u003c10);\n       // Sort by score in descending order and take top 10\n       return filtered.topk(filtered.score,10,true); \n    } \n  )\";\n\n  // 3. Compile to generate Function, the generated Function object can be saved for subsequent use\n  rapidudf::JitCompiler compiler;\n  // CompileFunction's template parameters support multiple types, the first template parameter is the return type, the rest are function parameter types\n  auto result = compiler.CompileFunction\u003csimd::Table*, Context\u0026, simd::Table*\u003e(source);\n  if (!result.ok()) {\n    RUDF_ERROR(\"{}\", result.status().ToString());\n    return -1;\n  }\n  auto f = std::move(result.value());\n\n  // 4.1 Test data, need to convert raw data into columnar data\n  std::vector\u003cStudent\u003e students;\n  for (size_t i = 0; i \u003c 128; i++) {\n    float score = (i + 1) % 150;\n    uint16_t age = i % 5 + 8;\n    bool gender = i % 2 == 0;\n    students.emplace_back(Student{\"test_\" + std::to_string(i), age, score, gender});\n  }\n  // 4.2 Create table instance\n  rapidudf::Context ctx;\n  auto table = schema-\u003eNewTable(ctx);\n  std::ignore = table-\u003eAddRows(students);\n\n  // 5. Execute function\n  auto result_table = f(ctx, table.get());\n  auto result_scores = result_table-\u003eGet\u003cfloat\u003e(\"score\").value();\n  auto result_names = result_table-\u003eGet\u003cStringView\u003e(\"name\").value();\n  auto result_ages = result_table-\u003eGet\u003cuint16_t\u003e(\"age\").value();\n  auto result_genders = result_table-\u003eGet\u003cBit\u003e(\"gender\").value();\n  for (size_t i = 0; i \u003c result_scores.Size(); i++) {\n    RUDF_INFO(\"name:{},score:{},age:{},gender:{}\", result_names[i], result_scores[i], result_ages[i],\n              result_genders[i] ? true : false);\n  }\n  return 0;\n};\n```\n\n### Dynamic Vector Table Based on Protobuf/Flatbuffers/Struct\n**RapidUDF** can also create a table from Protobuf/Flatbuffers, avoiding the tedious process of creating a TableSchema. Building table instances can be done directly from arrays of Protobuf objects such as `std::vector\u003cT\u003e, std::vector\u003cconst T*\u003e, std::vector\u003cT*\u003e`.\n\nHere is an example of creating a vector table based on Protobuf;  \nExamples based on flatbuffers can be found in [fbs_vector_table_udf](rapidudf/examples/fbs_vector_table_udf.cc);    \nExamples based on struct can be found in [struct_vector_table_udf](rapidudf/examples/struct_vector_table_udf.cc);    \n```cpp\n#include \"rapidudf/examples/student.pb.h\"\n#include \"rapidudf/rapidudf.h\"\n\nusing namespace rapidudf;\nint main() {\n  // 1. Create table schema\n  auto schema = simd::TableSchema::GetOrCreate(\n      \"Student\", [](simd::TableSchema* s) { std::ignore = s-\u003eAddColumns\u003cexamples::Student\u003e(); });\n\n  // 2. UDF string\n  std::string source = R\"(\n    table\u003cStudent\u003e select_students(Context ctx, table\u003cStudent\u003e x) \n    { \n       auto filtered = x.filter(x.score \u003e90 \u0026\u0026 x.age\u003c10);\n       // Sort in descending order\n       return filtered.topk(filtered.score,10, true); \n    } \n  )\";\n\n  // 3. Compile to generate Function, the generated Function object can be saved for subsequent use\n  rapidudf::JitCompiler compiler;\n  auto result = compiler.CompileFunction\u003csimd::Table*, Context\u0026, simd::Table*\u003e(source);\n  if (!result.ok()) {\n    RUDF_ERROR(\"{}\", result.status().ToString());\n    return -1;\n  }\n  auto f = std::move(result.value());\n\n  // 4.1 Test data\n  std::vector\u003cexamples::Student\u003e students;\n  for (size_t i = 0; i \u003c 150; i++) {\n    examples::Student student;\n    student.set_score((i + 1) % 150);\n    student.set_name(\"test_\" + std::to_string(i));\n    student.set_age(i % 5 + 8);\n    students.emplace_back(std::move(student));\n  }\n  // 4.2 Create table instance and populate data\n  rapidudf::Context ctx;\n  auto table = schema-\u003eNewTable(ctx);\n  std::ignore = table-\u003eAddRows(students);\n\n  // 5. Execute function\n  auto result_table = f(ctx, table.get());\n  // 5.1 Fetch columns\n  auto result_scores = result_table-\u003eGet\u003cfloat\u003e(\"score\").value();\n  auto result_names = result_table-\u003eGet\u003cStringView\u003e(\"name\").value();\n  auto result_ages = result_table-\u003eGet\u003cint32_t\u003e(\"age\").value();\n\n  for (size_t i = 0; i \u003c result_scores.Size(); i++) {\n    RUDF_INFO(\"name:{},score:{},age:{}\", result_names[i], result_scores[i], result_ages[i]);\n  }\n  return 0;\n};\n```\n\n### Compilation Cache\n**RapidUDF** incorporates an LRU cache with keys as the string of expressions/UDFs. Users can retrieve compiled JitFunction objects from the cache to avoid parse/compile overhead each time they are used:\n```cpp\nstd::vector\u003cint\u003e vec{1, 2, 3};\n  JitCompiler compiler;\n  JsonObject json;\n  json[\"key\"] = 123;\n\n  std::string content = R\"(\n    bool test_func(json x){\n      return x[\"key\"] == 123;\n    }\n  )\";\n  auto rc = GlobalJitCompiler::GetFunction\u003cbool, const JsonObject\u0026\u003e(content);\n  ASSERT_TRUE(rc.ok());\n  auto f = std::move(rc.value());\n  ASSERT_TRUE(f(json));\n  ASSERT_FALSE(f.IsFromCache());  // 第一次编译\n\n  rc = GlobalJitCompiler::GetFunction\u003cbool, const JsonObject\u0026\u003e(content);\n  ASSERT_TRUE(rc.ok());\n  f = std::move(rc.value());\n  ASSERT_TRUE(f(json));\n  ASSERT_TRUE(f.IsFromCache());  //后续从cache中获取\n```\n\n### More Examples and Usage\n- [Using Custom C++ Classes in Expressions/UDFs](docs/ffi.md)\n- [Using Member Functions of Custom C++ Classes in Expressions/UDFs](docs/ffi.md)\n- [Using Protobuf Objects in Expressions/UDFs](docs/ffi.md)\n- [Using FlatBuffers Objects in Expressions/UDFs](docs/ffi.md)\n- [Using STL Objects in Expressions/UDFs](docs/ffi.md)\n\nThere are more examples for different scenarios in the [tests](rapidudf/tests/) code directory.\n\n## Performance\n\n### Comparison with Native C++\nSince RapidUDF is based on LLVM Jit, it theoretically can achieve performance very close to native C++ code. Comparison results for compiling the Fibonacci method with `O0`:\n```\nBenchmark                     Time             CPU   Iterations\n---------------------------------------------------------------\nBM_rapidudf_fib_func      22547 ns        22547 ns        31060\nBM_native_fib_func        38933 ns        38933 ns        17964\n```\nFibonacci method GCC `O2` compilation comparison results:\n```\nBenchmark                     Time             CPU   Iterations\n---------------------------------------------------------------\nBM_rapidudf_fib_func      22557 ns        22555 ns        31065\nBM_native_fib_func        19246 ns        19239 ns        36395\n```\nNote: The Jit implementation currently uses the same jit compilation logic under `O0/O2` compilation switches; theoretically, the generated code should be identical.\n\n### Vectorized Acceleration Scenarios\nThe following tests were run on a CPU that supports `AVX2`, with the compilation optimization flag `O2`, and an array length of `4099`.\n#### Complex Trigonometric Expression\nThe calculation is to execute the double array `x + (cos(y - sin(2 / x * pi)) - sin(x - cos(2 * y / pi))) - y`; theoretically, the acceleration ratio should be the multiple of the `AVX2` register width to the `double` width, which is `4`.    \nActual results are as follows, showing that the acceleration ratio has exceeded `4` times, reaching **6.09**:\n```\nBenchmark                               Time             CPU   Iterations\n-------------------------------------------------------------------------\nBM_rapidudf_expr_func              207713 ns       207648 ns         3362\nBM_rapidudf_vector_expr_func        33962 ns        33962 ns        20594\nBM_native_func                     207145 ns       207136 ns         3387\n```\n#### Wilson Ctr\nOriginal function prototype:\n```cpp\nfloat  wilson_ctr(float exp_cnt, float clk_cnt) {\n  return std::log10(exp_cnt) *\n         (clk_cnt / exp_cnt + 1.96 * 1.96 / (2 * exp_cnt) -\n          1.96 / (2 * exp_cnt) * std::sqrt(4 * exp_cnt * (1 - clk_cnt / exp_cnt) * clk_cnt / exp_cnt + 1.96 * 1.96)) /\n         (1 + 1.96 * 1.96 / exp_cnt);\n}\n```\n\nCorresponding vector UDF script implementation:\n```cpp\n    simd_vector\u003cf32\u003e wilson_ctr(Context ctx, simd_vector\u003cf32\u003e exp_cnt, simd_vector\u003cf32\u003e clk_cnt)\n    {\n       return log10(exp_cnt) *\n         (clk_cnt / exp_cnt +  1.96 * 1.96 / (2 * exp_cnt) -\n          1.96 / (2 * exp_cnt) * sqrt(4 * exp_cnt * (1 - clk_cnt / exp_cnt) * clk_cnt / exp_cnt + 1.96 * 1.96)) /\n         (1 + 1.96 * 1.96 / exp_cnt);\n    }\n```\nTheoretically, the acceleration ratio should be the multiple of the `AVX2` register width to the float width, which is `8`;     \nActual results are as follows, showing that the acceleration ratio has exceeded `8` times, reaching **10.5**:\n```\nBenchmark                               Time             CPU   Iterations\n-------------------------------------------------------------------------\nBM_native_wilson_ctr                69961 ns        69957 ns      9960\nBM_rapidudf_vector_wilson_ctr       6661 ns         6659 ns       105270\n```\n\n\n## Dependencies\n\n- [LLVM](https://llvm.org/)\n- [highway](https://github.com/google/highway)\n- [x86-simd-sort](https://github.com/intel/x86-simd-sort)\n- [sleef](https://github.com/shibatch/sleef)\n- [fmtlib](https://github.com/fmtlib/fmt)\n- [spdlog](https://github.com/gabime/spdlog)\n- [abseil-cpp](https://github.com/abseil/abseil-cpp)\n- boost\n  - [preprocessor](http://boost.org/libs/preprocessor)\n  - [parser](https://github.com/tzlaine/parser)\n- [protobuf](https://github.com/protocolbuffers)\n- [flatbuffers](https://github.com/google/flatbuffers)\n- [json](https://github.com/nlohmann/json)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyinqiwen%2Frapidudf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyinqiwen%2Frapidudf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyinqiwen%2Frapidudf/lists"}