
Carquet 发布:用纯 C 实现 Parquet 文件读写的高性能库
来源标题:Carquet · Parquet at the Speed of C
Carquet 是一个用纯 C 编写的小型 Parquet 读写库,无需运行时和垃圾回收器。它利用运行时检测的向量指令解码列数据,并从内存映射直接读取未压缩页,支持 Linux、macOS 和 Windows 及多种编译器。用户可通过 git clone 后 make 构建并安装,获得静态库、头文件、CLI 和 pkg-config 支持。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
一个极小且自包含的库,用于读写 Parquet 文件。没有运行时,没有垃圾回收器,没有 50 MB 的框架:只有紧凑的 C 代码,编译成你的 CPU 最擅长的指令。
C 语言在你的数据和硅片之间没有任何中间层。Carquet 直接利用这一点:列解码使用运行时选择的向量指令,未压缩的页直接从内存映射中提供,内存来自 arena 而不是成千上万次小分配。
Parquet 解码的热循环被写成向量内核,覆盖所有主流指令集。一个可移植的二进制文件在启动时检测 CPU,并分派到它支持的最宽内核,其余情况使用标量回退。
支持 Linux、macOS 和 Windows。支持 GCC、Clang、MSVC 和 MinGW。缺失的编解码器会自动获取并构建。
最简单的路径:克隆、make、安装。你会得到静态库、头文件、carquet CLI、一个 pkg-config 文件以及 find_package(carquet) 支持。
$ git clone https://github.com/Vitruves/carquet.git && cd carquet $ make # 优化构建(make native 用于 -march=native) $ sudo make install # /usr/local,或 make install PREFIX=/opt/carquet $ cc app.c $(pkg-config --cflags --libs carquet)
预构建的 wheel 为 Linux、macOS 和 Windows 打包了该库。表通过 Arrow C Data Interface 以 pyarrow.Table 返回。
$ pip install carquet
将固定版本的发布直接拉入你的 CMake 项目,无需安装步骤。
include(FetchContent) set(CARQUET_BUILD_CLI OFF CACHE BOOL "" FORCE) FetchContent_Declare(carquet GIT_REPOSITORY https://github.com/Vitruves/carquet.git GIT_TAG v0.7.3) FetchContent_MakeAvailable(carquet) target_link_libraries(app PRIVATE carquet)
直接驱动 CMake 以自定义选项:共享库、-march=native、关闭 Brotli、你自己的 zstd/zlib/lz4 目标。
$ cmake -B build -DCMAKE_BUILD_TYPE=Release -DCARQUET_BUILD_SHARED=ON $ cmake --build build -j $ sudo cmake --install build
等效的 xmake 构建,具有相同的选项和默认值。
$ xmake # 静态库 + CLI,release $ xmake f --native_arch=y # 可选:-march=native $ xmake install -o /usr/local
为 MSVC、clang-cl 和 MinGW-w64 提供预设,支持静态或共享。从 Developer PowerShell 运行。
PS> cmake --preset msvc PS> cmake --build --preset msvc PS> ctest --preset msvc
API 131 个纯 C 函数,错误以值返回,没有需要采纳的框架。下面的每个片段都可以用 -Wall -Wextra -Werror 原样编译。
定义 schema,然后给每一列一个纯 C 数组。可空列每行接受一个定义级别,并且只接受存在的值。
#include <carquet/carquet.h> #include <stdio.h> #define TRY(x) do { if ((x) != CARQUET_OK) return 1; } while (0) int main(void) { carquet_error_t err = CARQUET_ERROR_INIT; carquet_logical_type_t utf8 = { .id = CARQUET_LOGICAL_STRING }; carquet_schema_t* schema = carquet_schema_create(&err); TRY(carquet_schema_add_column(schema, "id", CARQUET_PHYSICAL_INT64, NULL, CARQUET_REPETITION_REQUIRED, 0, 0)); TRY(carquet_schema_add_column(schema, "name", CARQUET_PHYSICAL_BYTE_ARRAY, &utf8, CARQUET_REPETITION_REQUIRED, 0, 0)); TRY(carquet_schema_add_column(schema, "score", CARQUET_PHYSICAL_DOUBLE, NULL, CARQUET_REPETITION_OPTIONAL, 0, 0)); carquet_writer_options_t opts; carquet_writer_options_init(&opts); opts.compression = CARQUET_COMPRESSION_ZSTD; opts.write_page_index = true; /* 让读取器可以跳过页 / carquet_writer_t w = carquet_writer_create("people.parquet", schema, &opts, &err); if (!w) { fprintf(stderr, "%s\n", err.message); return 1; } int64_t ids[] = {1, 2, 3}; carquet_byte_array_t names[] = {{(uint8_t*)"ada", 3}, {(uint8_t*)"linus", 5}, {(uint8_t*)"grace", 5}}; double scores[] = {9.5, 7.25}; /* 仅存在的值 / int16_t defs[] = {1, 0, 1}; / 第 2 行为 NULL */ TRY(carquet_writer_write_batch(w, 0, ids, 3, NULL, NULL)); TRY(carquet_writer_write_batch(w, 1, names, 3, NULL, NULL)); TRY(carquet_writer_write_batch(w, 2, scores, 3, defs, NULL)); TRY(carquet_writer_close(w)); carquet_schema_free(schema); return 0;
批量读取器只解码你请求的列,并行进行,并交给你类型化指针以及一个 Arrow 风格的 validity 位图。
#include <carquet/carquet.h> #include <stdio.h> #define TRY(x) do { if ((x) != CARQUET_OK) return 1; } while (0) int main(void) { carquet_error_t err = CARQUET_ERROR_INIT; carquet_reader_t* r = carquet_reader_open("people.parquet", NULL, &err); if (!r) { fprintf(stderr, "%s\n", err.message); return 1; } carquet_batch_reader_config_t cfg; carquet_batch_reader_config_init(&cfg); const char* cols[] = {"name", "score"}; /* 只解码这些列 / cfg.column_names = cols; cfg.num_column_names = 2; cfg.batch_size = 65536; carquet_batch_reader_t br = carquet_batch_reader_create(r, &cfg, &err); carquet_row_batch_t* batch = NULL; while (carquet_batch_reader_next(br, &batch) == CARQUET_OK && batch) { const void *names, *scores; const uint8_t *nn, valid; int64_t n; TRY(carquet_row_batch_column(batch, 0, &names, &nn, &n)); TRY(carquet_row_batch_column(batch, 1, &scores, &valid, &n)); const carquet_byte_array_t s = names; for (int64_t i = 0; i < n; i++) { if (valid && !(valid[i / 8] & (1u << (i % 8)))) printf("%.*s NULL\n", (int)s[i].length, s[i].data); else printf("%.s %.2f\n", (int)s[i].length, s[i].data, ((const double)scores)[i]); carquet_row_batch_free(batch); batch = NULL; carquet_batch_reader_free(br); carquet_reader_close(r); return 0; ada 9.50 · linus NULL · grace 7.25
谓词被下推:行组通过统计信息和 bloom filter 裁剪,页通过 page index 裁剪,这一切都发生在任何解压缩之前。
#include <carquet/carquet.h> #include <stdio.h> #define TRY(x) do { if ((x) != CARQUET_OK) return 1; } while (0) int main(void) { carquet_error_t err = CARQUET_ERROR_INIT; carquet_reader_t* r = carquet_reader_open("people.parquet", NULL, &err); if (!r) { fprintf(stderr, "%s\n", err.message); return 1; } carquet_batch_reader_config_t cfg; carquet_batch_reader_config_init(&cfg);
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。