fastparquet 的最新功能

Martin Durant 是 Anaconda 的高级软件工程师。在这篇博文中,他详细介绍了 fastparquet 的背景、改进计划、依赖关系和升级。继续阅读,了解 Martin 如何努力改善 fastparquet 的用户体验和读取端性能。


在过去六个月中,我投入了大量精力来现代化和改进 fastparquet,这是一个 Python parquet 读/写软件包,已为 PyData 社区服务了五年。凭借这些新增功能,现在可以使用几种功能,很多事情更快了,最重要的是,没有任何事情变慢了(至少在读取端!)。

关于我的一些背景:我从天体物理学家开始我的职业生涯,并在多个学术职位工作,包括医学成像研究。之后,我成为一名数据科学家,并且一直是开源 Dask、Intake、Streamz、fsspec 和 Zarr 维护团队的成员,专门从事数据访问、远程文件系统和数据格式。我在 Anaconda 工作了六年,主要作为开源团队的软件工程师。

fastparquet 的背景

fastparquet 是第一个用于 parquet 格式大数据的读写器,它可以在 Python 中运行,无需使用 Spark 或其他非 Python 工具。它提供了对 pandas DataFrame 的高性能转换,并与 Dask 很好地集成。在 pyarrow 的 parquet 集成出现之前,它是唯一一个矢量化的 parquet 引擎(另请参见早期的 parquet-python,它共享了一些代码)。

截至 2021 年初,fastparquet 仍然是 pandas-parquet 的主要软件包之一,根据 pypistats 的数据,每月下载量约为 100 万次。然而,它在一段时间内都没有看到任何实质性的发展。

fastparquet 的改进计划

2021 年 4 月,我在 问题 中详细说明了需要实施的重要改进。此列表在过去几个月里得到了补充,但大多数项目都是最初的形式。如您所见,几乎所有项目都已完成(另请参见下面的“未来”部分)。

完成各种任务的实施导致了一系列版本发布,在这里我们将比较 0.7.1 和 0.5.0 版本的读取性能。

依赖关系

cramjam 软件包出现在 2020 年,它链接了 parquet 所需压缩编解码器的 Rust 实现(除了 LZO,没有人使用它!)。这简化了 fastparquet 的依赖关系,用户不再需要为 snappy、brotli 或 zstd 寻找额外的软件包。此外,cramjam 在某些情况下可能更快。

fastparquet 最初是使用 Numba 提供的加速构建的。这对对 parquet 规范中可能的各种字节表示进行编码/解码非常有效。不幸的是,Numba 不处理(Python)字符串,因此也存在一些 Cython 代码。

我们将所有 Numba 代码重写为 Cython。这并没有导致更好的性能,但减少了所需环境的大小(没有 LLVM!)并消除了一些运行时编译。

最后,在 PR 中,我们即将消除对 thrift 的需求,使用我们自己的实现。

Conda 环境大小

(使用最新的 Python、pandas 等以及包含 0.5.0 版本的压缩器)

新进展

​我们对 fastparquet 做了一些更改以改进它,因此我概述了一些使用户体验更佳的亮点。

  • 加载没有“_metadata”文件的目录:这始终是可能的,但需要用户首先显式地获取文件列表。

  • 读取和写入“数据页面头 2”:很久以前就添加到 parquet 规范中。只有少数框架开始生成 V2 文件,但我们需要能够读取它们。它也恰好是,使用 V2 会带来真正的性能优势。

  • 编码类型 RLE_DICT 和 DELTA,以实现兼容性。

  • 可空类型和 ns 解析时间,以更好地匹配 pandas 和 parquet 类型系统。可空类型默认启用,因此可选 int 列在读取时将成为 pandas 可空 Int 列,但将转换为 float 并使用 NaN 标记空值的老行为仍然可用。

  • 行过滤:仅为实际需要的数据生成 DataFrame。这是一个两步过程,一步加载要过滤的列,一步应用过滤器。目标不是速度,而是更低的内存占用(因为 parquet 是一种块状格式,您最终会读取相同数量的字节)。

性能

​大部分精力都花在了这里,特别是读取端的性能。以下是几个选定的基准测试。请记住,基准测试是有偏差的,并且使用了相同的测量技术来指导优化过程。数字是我的机器给出的。

打开数据集“split”

这是 fastparquet 的测试套件数据集之一,包含许多文件,但数据量很少。时间单位为毫秒。

fsspec 在列出和获取有关本地文件的信息方面做了一些改进。同样,当没有全局元数据可用时,远程文件将被并发获取,从而减轻延迟的影响。

首次读取时间

导入、打开和读取相同“split”数据集的时间,以秒为单位(这个数字比上一节中的数字大得多!)。

差异主要是因为 0.7.1 版本没有运行时编译。导入时间大致相同,主要由 pandas 决定。

列读取时间

这基于 fastparquet/benchmarks/columns.py 中的基准测试脚本,使用随机数据的列。与上面的“split”数据集相比,这些数据集的数据量足够大,因此文件打开和元数据解析可以忽略不计。时间以相对单位表示。

我们可以看到,0.7.1 通常更快或相同(除了 bool 类型略慢),并且使用 V2 页面可以对 float 和时间(以及未包含的 int32/64)产生重大影响。我们还看到 zstd 压缩是相同或更好的,并且同样,V2 可以产生积极的影响。对于 gzip(始终很慢)和 snappy(始终很快,但对随机数据的压缩效率不高)来说,这种差异并不明显。

未来

4 月计划中唯一剩下的主要工作是独立于 thrift 软件包的 thrift 读取/写入实现。PR#663 进行了这项工作,虽然仍有一些粗糙的地方,但原始性能非常令人鼓舞

在内存中解析“split”数据集元数据块字节的时间,以毫秒为单位
使用 pickle.dumps/pickle.loads 的往返时间,以毫秒为单位

除了上面详细介绍的内容之外,其他外部更改也在后台发生。虽然一些更改需要修复,但其他更改则提供了继续改进 fastparquet 的性能和功能的机会。我对我们今天取得的进展感到兴奋,并期待在未来分享关于该项目持续进展的更新。

与专家交谈

与我们的专家交谈,为您的 AI 之旅寻找解决方案。

与专家交谈