立即下载 DOCA,开启高性能AI 网络之旅,实现“一站式” 编程
数据科学

使用 RAPIDS 的 Parquet 字符串数据的编码和压缩指南

Parquet Writer 提供了默认关闭的编码和压缩选项。启用这些选项可以为数据提供更好的无损压缩,但了解用于特定用例的选项对于确保它们按预期执行是至关重要的。

在本文中,我们将探讨哪些编码和压缩选项最适合您的字符串数据。字符串数据在数据科学中无处不在,用于表示小片段信息,如名称、地址和数据标签,以及大片段信息,如 DNA 序列、JSON 对象和完整文档。

首先,我们解释每个选项。

  • 编码步骤重新组织数据,以减少其字节大小,同时保留对每个数据点的访问权限。
  • 压缩步骤进一步减少了以字节为单位的总大小,但每个压缩块必须先被解压缩,然后数据才能再次被访问。

在 Parquet 格式中,有两种 delta 编码,旨在优化字符串数据的存储。为了帮助分析每个选项,我们构建了一项工程研究,使用 libcudf 和 cudf.pandas 对来自公开来源的字符串数据进行分析,以比较 Parquet 的编码和压缩方法的有效性,使用文件大小、读取时间和写入时间作为指标。

什么是 RAPIDS libcudf 和 cudf.pandas?

RAPIDS 开源加速数据科学库套件中,libcudf 是用于列式数据处理的 CUDA C++ 库。RAPIDS libcudf 基于 Apache Arrow 内存格式,支持 GPU 加速的读取器、写入器、关系代数函数和列转换。

在本文中,我们使用 parquet_io C++ 示例 来演示 libcudf API,并评估编码和压缩方法。对于读/写吞吐量,我们使用 Python read_parquet 函数来显示零代码更改性能结果,比较 pandas 和 RAPIDS cudf.pandas 的性能。RAPIDS cudf.pandas 是一个开源的 GPU 加速数据帧库,可以将现有 pandas 代码加速高达 150 倍。

Kaggle 字符串数据和基准测试

字符串数据非常复杂,编码和压缩的有效性取决于数据本身。

考虑到这一点,我们很早就决定编译一个字符串列数据集,以比较编码和压缩方法。最初,我们探索了几种数据生成器,但发现数据生成器中有关字符串可压缩性、基数和长度的决定对文件大小结果产生了巨大影响。

作为数据生成器的替代方案,我们基于公共数据集组装了一个包含 149 个字符串列的数据集,总文件大小为 4.6 GB,总字符数为 12 亿。我们对每个编码和压缩选项进行了比较,比较的项目包括文件大小、读取时间和写入时间。

我们在本文中使用 parquet-cpp-arrow 16.1.0 对 RAPIDS libcudf 24.08 和 pandas 2.2.2 中的 Parquet 读取器/写入器堆栈进行了比较。我们发现 libcudf 和 arrow-cpp 之间的编码大小差异小于 1%。与 libzstd 1.4.8+dfsg-3build1 相比,在 nvCOMP 3.0.6 中使用 ZSTD 实现时,文件大小增加了 3%至 8%。

总体而言,我们发现本文中关于编码和压缩选择的结论对 CPU Parquet 编写者和 GPU Parquet 编写者均适用。

Parquet 中的字符串编码

在 Parquet 中,字符串数据使用字节数组物理类型表示。这意味着字符串数据是以字节的形式存储的。有关编码布局的更多信息,请参阅 编码

原始字节数组表示包括单字节和多字节字符。Parquet 中有多种编码方法可用于字节数组数据,其中大多数编写者默认对字符串数据采用 RLE_DICTIONARY 编码。

字典编码使用字典页面将字符串值映射到整数,然后写入使用此字典的数据页面。如果字典页面增大(通常超过 1MiB),则 Writer 会回退到 PLAIN 编码。在这里,32 位大小与原始字节数组交错。

在 Parquet V2 规范中,针对字节数组物理类型,添加了两种新的编码,这两种编码可用于对字符串数据进行编码。

  • DELTA_LENGTH_BYTE_ARRAY (DLBA):相比 PLAIN 编码,DLBA 提供了一个简单的改进。它使用 DELTA_BINARY_PACKED 对整数大小的数据进行分组并进行编码,并将字节数组数据连接到一个缓冲区中。DLBA 编码的目标是帮助压缩阶段实现更好的压缩比,即通过对类似数据进行分组。
  • DELTA_BYTE_ARRAY(DBA):存储前一个字符串的前缀长度和后缀字节数组。DBA 编码在排序或半排序数据中效果很好,其中许多字符串值与前一行共享部分前缀。

按编码和压缩划分的总文件大小

Bar chart shows the file size sum in GB for NONE, SNAPPY and ZSTD compression methods with default, DLBA, and DBA encoding methods. “Choose best” brings ~3% file size reduction versus “default” by selecting the encoding that yields the smallest file size for each file.
图 1. RAPIDS libcudf Parquet Writer 中 149 个字符串列的总文件大小按编码方法和压缩方法划分

默认情况下,大多数 Parquet 编写者对字符串列使用字典编码和 SNAPPY 压缩。对于数据集中的 149 个字符串列,默认设置会产生总计 4.6 GB 的文件大小。对于压缩,我们发现 ZSTD 的性能优于 SNAPPY,而 SNAPPY 优于 NONE。

对于这组字符串列,压缩对文件大小的影响比编码更大,其中PLAIN-SNAPPYPLAIN-ZSTD的表现优于未压缩条件。

此数据集的最佳单一设置为 default-ZSTD。通过对有好处的文件选择增量编码,可以进一步将文件大小总和减少 2.9%。如果仅过滤平均字符串长度少于 50 个字符的字符串列,选择 best-ZSTD 将显示文件大小总和相对于 default-ZSTD 减少 3.8%(从 1.32 GB 到 1.27 GB)。

何时为字符串选择增量编码

默认的字典编码方法适用于具有低基数和短字符串长度的数据。然而,对于具有高基数或长字符串长度的数据,通常可以通过增量编码来缩小文件大小。

对于 arrow-cppparquet-javacudf >=24.06,字典编码使用 1-MiB 字典页面大小限制。如果行组中的不同值可以适合此大小,字典编码可能会生成最小的文件大小。然而,高基数数据不太可能适合 1-MiB 限制,因为长字符串会限制适合 1-MiB 限制的值的数量。

Scatter plot showing the optimal encoding method when using ZSTD compression for each of 149 string columns. For each point, the x-axis shows “chars/string (average),” the y-axis shows “distinct count”, and the shape of each point indicates whether the best encoding was “Dictionary with plain fallback”, “Delta length byte array” or “Delta byte array”.
图 2. 使用 ZSTD 压缩时的字符串数据最佳编码方法

在图 2 中,每个点分别代表一个字符串列,并根据该列的两个属性进行绘制:distinct countchar/string 的平均值。虚线椭圆形被放置以突出显示近似聚类。

对于字符串列,当增量编码产生最小文件大小时,我们发现文件大小缩减最为明显的是当每个字符串的平均字符数少于 50 时。字符数量包括多字节字符。

DLBA 编码的主要好处是将所有大小数据集中到单个缓冲区中,从而提高存储效率。特别是对于短字符串列,数据大小所占的比例更高。DBA 编码也显示了对平均每字符串约 50 个字符的字符串列的最大文件大小缩减,例如有一种情况比字典编码的默认值小 80%。

纵观这些示例,对于具有排序或半排序值(例如升序 ID、格式化时间和时间片段)的列,DBA 编码可提供最大的文件大小缩减。

Scatter plot of file size reduction versus chars/string (average), measured as delta-encoded ZSTD-compress versus dictionary-encoded ZSTD-compressed. Both “Delta length byte array” and “Delta byte array” encoding methods are shown.
图 3. 使用增量编码相比具有普通回退的字典编码,可以减少文件大小。

在图 3 中,每个点都代表了一个文件,该文件经过 delta 编码和 ZSTD 压缩后获得了最小的文件大小。虚线是数据的二次最佳拟合线。

读取器和写入器性能

除了文件大小的数据外,我们还收集了文件写入时间和读取时间的数据,其中,GPU 加速的 cudf.pandas 库的性能比 pandas 好。

我们没有将 C++ 与 Python 脚本运行时进行比较,而是使用相同的 Python 脚本与 pandas 和零代码更改 cudf.pandas 测量了 Parquet 文件处理吞吐量。字符串数据集包括 149 个文件,其中字符串数据的总字符数为 12 亿,使用最佳编码和压缩方法时磁盘上的总文件大小为 2.7 GB。文件读取和写入时间是使用 Samsung 1.9TB PCI-E Gen4 NVMe SSD 数据源、Intel Xeon Platinum 8480CL CPU 和 NVIDIA H100 80GB HBM3 GPU 收集的。处理时间测量为执行读取或写入步骤的时间,并对所有文件进行求和。在读取每个文件之前,操作系统缓存被清除。

  • 使用 pyarrow Parquet 引擎的 Pandas 显示了 22 MB/s 的读取吞吐量和 27 MB/s 的写入吞吐量。
  • Cudf.pandas 在 24.06 中使用默认的 CUDA 内存资源,显示读取吞吐量达到 390 MB/s,写入吞吐量达到 200 MB/s。
  • 将 cudf.pandas 与 RMM 池 结合使用时(通过设置环境变量 CUDF_PANDAS_RMM_MODE="pool"),我们观察到读取吞吐量达到 552 MB/s,写入吞吐量达到 263 MB/s。
Bar chart showing Parquet file write and read throughput for pandas, cudf.pandas, and cudf.pandas with an RMM pool. The Parquet files used “Choose best” encoding and ZSTD compression.
图 4. 字符串数据集的 Parquet 文件处理吞吐量(以 MB/s 为单位)

字符串数据的编码和压缩指南

基于本文中的比较,我们推荐在处理字符串数据时使用以下编码和压缩设置。

  • 对于编码,Parquet 中字符串的默认字典编码非常适合处理每列少于 ~100K 个独立值的字符串数据。
  • 当独立值超过 10 万个时,增量增量长度编码通常会产生最小的文件大小。增量增量长度编码提供了最大的优势(10-30% 小文件),对于短字符串(每字符串少于 30 个字符)。
  • 对于压缩而言,无论采用何种编码方法,ZSTD 都能生成较小的文件大小,相比 Snappy 和未压缩选项,因此是一个很好的选择。

除了文件大小的数据外,我们还收集了文件写入时间和读取时间的数据,结果表明,GPU 加速的 cudf.pandas 相比默认的 pandas,其 Parquet 读取速度提高了 17-25 倍。

结束语

如果您正在寻找最快的方法来试用 GPU 加速的 Parquet 读取器和写入器,请参阅 RAPIDS cudf.pandasRAPIDS cuDF,以了解 Google Colab 上的加速数据科学。

RAPIDS libcudf 提供灵活的 GPU 加速工具,用于以 Parquet、ORC、JSON 和 CSV 等格式读取和写入列式数据。要开始使用 RAPIDS libcudf,请构建和运行一些 示例。如果您已在使用 cuDF,可以访问 GitHub 上的 /rapidsai/cudf/tree/HEAD/cpp/examples/parquet_io,构建和运行新的 C++ parquet_io 示例。

有关 CUDA 加速数据帧的更多信息,请参阅 cuDF 文档/rapidsai/cudf GitHub 存储库。为了更轻松地进行测试和部署, RAPIDS Docker 容器 也适用于版本和夜间构建。要参与讨论,请参阅 RAPIDS Slack 工作空间

致谢

非常感谢劳伦斯利弗莫尔国家实验室的 Ed Seidl,他为 RAPIDS libcudf 贡献了 V2 标头支持、增量编码器和解码器,以及许多关键功能。

 

标签