Polars 2.0发布:SQL性能超DuckDB,默认流式引擎

Polars 2.0发布,SQL性能超DuckDB。新增磁盘溢出支持,处理超内存负载;TPC-H基准测试领先DataFusion与DuckDB,默认启用流式引擎并强化类型检查。

Polars 2.0 正式发布:SQL 性能超越 DuckDB,默认启用流式引擎

2026 年 10 月 6 日,高性能 DataFrame 库 Polars 正式发布了 2.0 版本。此次更新虽然未主打单一巨型功能,但在核心架构、SQL 支持及数据类型处理上进行了多项重要改进。

Polars 2.0 SQL性能领先DuckDB

本次发布的核心亮点包括:

  • 磁盘溢出支持(Out-of-Core):初始版本的核心外支持现已启用,允许数据溢出至磁盘以处理超内存工作负载。
  • 显著的性能提升:针对核心组件进行了大量优化。
  • 一流的 SQL 支持:结合性能改进,Polars 在 TPC-H 和 TPC-DS 基准测试中表现优于 DataFusion 和 DuckDB。
  • 新增 Map 数据类型:原生支持 Arrow Map 类型。
  • 更严格的类型检查:在 dtypes 和明确性上采取更严格策略,从而加速反馈循环并提升 AI 代理的代码生成效率。

SQL 与性能:基准测试领先

Polars 团队将 SQL 视为“一流公民”。过去几个月里,Polars 的 SQL 覆盖面大幅扩展。得益于底层引擎的坚实构建,2.0 版本旨在通过优化器改进(如重新排序、更好的公共子表达式消除以及动态谓词/花括号处理)来支持更多 SQL 工作负载。

为了验证典型 SQL 场景下的表现,团队使用源自 TPC-H 和 TPC-DS1 的数据运行了 Polars SQL,并与以下版本进行对比:

  • DuckDB 1.5.6
  • DuckDB 2.0 alpha (2.0.0.dev2610011535)
  • DataFusion 54.0.0

测试环境与方法:

测试分别在 c7a.4xlarge(16 vCPUs,32GB RAM)和 c7a.metal(192 vCPUs,384GB RAM)实例上进行。每个查询在热启动模式下运行 5 次,每次查询使用独立进程,超时时间为 60 秒。在不同引擎/基准测试之间会清除文件缓存(但在各次查询之间不清除)。最终成绩取 5 次运行中的最佳值,并通过查询时间的总和及几何平均值进行比较。

数据由 tpcgen-cli parquet 来自 commit 99bedae 上的源编译生成。团队确认了 tpcgen-cli 的默认行组大小与 Polars scan_csv 通过 sink_parquet 和 Duckdb COPY 输出的大致相似性。SQL 查询由 DuckDB 1.5.6 的 tpch_queries 和 tpcds_queries 生成,数据存储于 EBS 上。

测试结果:

图表显示各引擎的运行时间(单位:秒,越低越好),按机器划分如下:

  • c7a.4xlarge (16 个 vCPU, 32GB)
  • c7a.metal (192 个 vCPU, 384GB)

在测试中,Polars 和两个 DuckDB 版本完成了所有查询。DataFusion 在 TPC-DS q72(以及之前的 q67)出现超时,且在 c7a.4xlarge 上的 TPC-H q18 出现内存溢出;因此这些特定查询被排除在结果之外。

观察结果显示,Polars 默认配置在除一个基准外的所有测试中均最快。当扩展到 192 个线程时,Polars 的开销保持不变。事实上,即便限制在 32 个核心,Polars 在所有基准测试中也具有竞争力或获胜。团队计划在下一版本中进一步优化大规模并行场景下的表现。完整的基准测试代码已开源:https://github.com/pola-rs/polars-2.0-benchmark。

默认流式引擎与磁盘溢出(OOC)

这是 2.0 版本影响最大的变化之一。LazyFrame 上的 collect() 调用现在默认使用流式引擎,这在大多数查询中能带来显著的内存节省和性能提升。

这一变更需要主版本号升级的原因在于:流式引擎不保证某些操作(如 join、group_by、unpivot 等)的默认行顺序。如果用户依赖可观察的行顺序,需显式设置 maintain_order=True。

同时,核心外(Out-of-Core,即溢出到磁盘)支持现已默认启用。当内存使用达到 80% 时开始触发溢出(该阈值可调)。目前,排序、窗口函数及许多表达式等操作已支持溢出到磁盘以完成查询,默认磁盘预算为 64GB。未来版本计划将 OOC 支持扩展至 Join 和 Group By 操作。

这两项变化使得 Polars 在高内存消耗的工作负载中更具弹性。

新增 Map 数据类型

Polars 现在直接支持 Arrow Map 类型作为其原生的 Map dtype。可以将 Map 理解为 Python 字典,用于将键映射到值。在 2.0 之前,Arrow MapType 在 Polars 中被读取为 List(Struct({"key": ..., "value": ...}))。

df = pl.DataFrame(
    {
        "user": ["alice", "bob", "carol"],
        "scores": pl.Series(
            [{"math": 90, "art": 75}, {"math": 60}, {}],
            dtype=pl.Map(pl.String, pl.Int64),
        ),
        "subject": ["art", "art", "math"],
    }
)
shape: (3, 3)
┌───────┬─────────────────────────┬─────────┐
│ user  ┆ scores                  ┆ subject │
│ ---   ┆ ---                     ┆ ---     │
│ str   ┆ map[str, i64]           ┆ str     │
╞═══════╪═════════════════════════╪═════════╡
│ alice ┆ {"math": 90, "art": 75} ┆ art     │
│ bob   ┆ {"math": 60}            ┆ art     │
│ carol ┆ {}                      ┆ math    │
└───────┴─────────────────────────┴─────────┘

新的 Map 类型支持类似字典的操作,包括固定键查找、从另一列获取键、包含检查、长度统计以及提取键值列表:

# Key lookups and dictionary-like methods:
df.select(
    "user",
    pl.col("scores").map.get("math").alias("math"),                # fixed key
    pl.col("scores").map.get(pl.col("subject")).alias("by_subject"),  # key from another column
    pl.col("scores").map.contains_key("art").alias("has_art"),
    pl.col("scores").map.len().alias("n"),
    pl.col("scores").map.keys().alias("keys"),
    pl.col("scores").map.values().alias("values"),
)
┌───────┬──────┬────────────┬─────────┬─────┬─────────────────┬───────────┐
│ user  ┆ math ┆ by_subject ┆ has_art ┆ n   ┆ keys            ┆ values    │
│ ---   ┆ ---  ┆ ---        ┆ ---     ┆ --- ┆ ---             ┆ ---       │
│ str   ┆ i64  ┆ i64        ┆ bool    ┆ u32 ┆ list[str]       ┆ list[i64] │
╞═══════╪══════╪════════════╪═════════╪═════╪═════════════════╪═══════════╡
│ alice ┆ 90   ┆ 75         ┆ true    ┆ 2   ┆ ["math", "art"] ┆ [90, 75]  │
│ bob   ┆ 60   ┆ null       ┆ false   ┆ 1   ┆ ["math"]        ┆ [60]      │
│ carol ┆ null ┆ null       ┆ false   ┆ 0   ┆ []              ┆ []        │
└───────┴──────┴────────────┴─────────┴─────┴─────────────────┴───────────┘

作为一种受支持的 dtype,Map 类型现在拥有专门的表达式支持。

更严格的类型系统与 AI 友好性

Polars 2.0 强调“严格”与“快速失败”的设计理念。错误应尽早抛出,而非在执行 20 分钟后才显现。隐含的数据不匹配行为不再是默认选项,因为这类不匹配可能隐藏潜在错误。

随着 AI 驱动开发的兴起,这种严格性变得更有价值。AI 代理可以通过调用 collect_schema() 方法早期验证查询结构。该方法能在无需执行任何数据的情况下解析类型并捕获模式级别的不匹配,从而确保快速反馈,使代理和人类开发者都能更快地迭代代码。

尽管并非所有错误都能在查询计划编译阶段被捕获(部分错误依赖于具体数据),但 Polars 在这些情况下也默认采取更严格的行为,以确保不一致性被捕获,而不是产生静默的错误结果。

后续规划与迁移指南

Polars 团队表示,未来几个月将继续完善路线图,目标是成为最快的分布式引擎。此外,GeoPolars 项目也已启动,预计近期会有更多消息公布。

若发现新版本存在问题,可通过 GitHub Issue 反馈:https://github.com/pola-rs/polars/issues。为了方便用户升级,官方已发布详细的迁移指南。

附录:基准测试详细数据

绝对数量(以秒为单位)详见原始报告。加粗数字代表每行中最快的引擎;图表展示了相对于最快引擎的速度差异。注意:32 线程的 Polars 仅在 c7a.metal 上运行。

扩展性分析:

在更大数据和更多核心的场景下,Polars 表现出良好的扩展性。在 SF100 数据集上,从 16 个 vCPU 移动到 192 个 vCPU 使得 Polars 在 TPC-H 上快 3.8 倍,在 TPC-DS 上快 2.2 倍(按总和计算)。相比之下,DuckDB 1.5.6 分别提速 3.2 倍和 1.9 倍,DuckDB 2.0 alpha 提速 2.2 倍和 1.5 倍,DataFusion 提速 1.7 倍和 1.0 倍。

在 SF10 数据集上,额外核心对 Polars 默认设置的帮助有限:它在 TPC-H 上保持同样速度,但在 TPC-DS 上慢 1.8 倍;而 DuckDB 1.5.6 仍然分别快 1.8 倍和 1.3 倍。(注:此处比较不包含仅在 c7a.metal 上运行的 32 线程 Polars 配置。)

脚注

  1. 这些基准测试基于 TPC-H 和 TPC-DS 标准,但由于测试方法不完全符合官方规范,所得结果无法直接与已公布的 TPC-H 和 TPC-DS 官方基准结果进行比较。

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读