← 文章 / 开源项目
Hacker News 3小时前 · 2026-10-06 22:31:52 · 14 阅读

Polars 2.0 正式发布

发布于 2026 年 10 月 6 日 星期二

今天正式推送 Polars 2.0。在早前的公告中,我们阐述了版本号升级的原因。这篇文章将重点介绍 2.0 带来的新特性。尽管我们并未刻意将其打造为大型功能更新,但其中仍有很多令人兴奋的亮点。

以下是本次版本的核心亮点:

  • 启用初始版本的非核心内存(spill-to-disk)支持,
  • 多项核心性能改进,
  • 一等公民级别的 SQL 支持,结合性能提升,使 Polars 在 TPC-H 和 TPC-DS1 基准测试中超越 DataFusion 和 DuckDB,
  • 新增 Map 数据类型,以及
  • 更严格的数据类型规范和显式性要求,从而加速反馈循环和 AI 迭代。

性能与 SQL 的一等公民地位

Polars 2.0 是一个里程碑,标志着我们将 SQL 提升为一等公民。过去几个月里,Polars 的 SQL 覆盖率大幅增加。过去两年间,我们一直在构建坚固的引擎。在 Polars 2.0 中,我们希望将其扩展到更多工作负载,包括 SQL。为了实现高性能,我们对优化器和引擎进行了多项改进,重点包括连接重排、更出色的公共子计划消除以及动态谓词/Bloom 过滤器。

为了查看在典型 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 vCPU,32GB 内存)和 c7a.metal(192 vCPU,384GB 内存)。每条查询在热启动状态下运行 5 次,每次查询使用独立进程,超时时间设置为 60 秒。每个引擎/基准测试之间清除文件缓存(查询之间不清除)。对于每条查询,我们取 5 次运行中的最佳成绩,并比较各引擎在这些查询时间上的总和及几何平均值。

数据由编译自提交 99bedae 的 tpcgen-cli parquet 生成。我们查看了 tpcgen-cli 的默认行组大小,并确认其与 Polars 通过 scan_csv 管道至 sink_parquet 以及 DuckDB 的 COPY 所产生的大小大致相似。SQL 查询由 DuckDB 1.5.6 的 tpch_queries() 和 tpcds_queries() 生成。数据存储于 EBS。

下图展示了各引擎的运行时间(以秒为单位,越低越好),按机器类型分组。

c7a.4xlarge(16 vCPUs,32 GB)

c7a.metal(192 vCPUs,384 GB)

Polars 及两个版本的 DuckDB 均完成了所有查询。DataFusion 在 TPC-DS q72(以及在 c7a.4xlarge 上的一次 q67)中发生超时,并在 TPC-H q18 上耗尽内存;因此,上述所有引擎的结果中均排除了这些查询。

我们观察到,除一项基准测试外,默认配置的 Polars 在所有测试中均为最快。当扩展到 192 个线程时,Polars 存在固定的开销,这影响了小数据查询的性能。事实上,我们将 Polars 限制在 32 个核心时,其在所有基准测试中都具有竞争力或获胜。我们已诊断出问题根源,并有望在下一个版本中修复此问题。关于基准测试的更多信息,请参见附录。我们鼓励大家复现我们的结果,并在此共享了用于此基准测试的仓库:https://github.com/pola-rs/polars-2.0-benchmark。

Streaming 引擎与 OOC 成为默认配置

这是 2.0 版本中最具影响力的变更之一。在 LazyFrame 上调用 collect 现在将默认使用 Streaming 引擎,从而在大多数查询上带来显著的内存和性能提升。这需要主要版本号升级的原因是,Streaming 引擎在某些操作(如 join、group_by、unpivot 等)中默认不保证行顺序。如果需要在这些操作中观察行顺序,可以通过设置 maintain_order=True 来启用该选项。

超内存计算(spill to disk)现已默认启用。当内存占用达到约 80% 时开始向磁盘溢写(这个阈值可能还需要调优)。目前支持超内存计算的操作(排序、窗口函数以及大量表达式)都可以通过溢写磁盘来完成查询。默认磁盘配额为 64GB。接下来我们还会为 join 和 group-by 也启用超内存计算。

这两项改进让 Polars 在高内存负载场景下对普通数据从业者来说稳定得多。而随着超内存的 join 和 group-by 提上日程,这种稳定性还会进一步提升。

新的 Map 数据类型

Polars 现在直接支持将 Arrow 的 MapType 作为 Polars 的 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    │
└───────┴─────────────────────────┴─────────┘
# 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 类型现在拥有专用表达式,如键查找、值遍历以及其他字典类方法。

更严格的 Polars

Polars 追求严格模式并快速失败。错误应尽早抛出,而不是在流水线运行 20 分钟后才出现。对于数据不匹配的隐式行为应默认关闭、显式开启,因为这些不匹配可能掩盖 bug。随着 AI 驱动开发的兴起,这种严格性变得更有价值。Agent 可通过调用 collect_schema() 提前验证查询结构,该方法能解析类型并在 schema 层面捕获不匹配,无需物化数据。这确保了快速反馈,让 Agent 和人类都能更快迭代。并非所有错误都能在查询计划编译阶段捕获,有些依赖于数据。在此类情况下,Polars 默认采用更严格的行为,以确保发现不一致性,而非静默产生不同结果。参见先前文章,其中包含 Polars 变得更严格的一些示例。

最后的话

Polars 2.0 发布,我们非常兴奋。未来几个月,我们将继续完善当前方向:改进 out-of-core 支持、提升大 CPU 数量下的扩展性,并在 Polars Cloud 上力争成为最快的分布式引擎。我们还开始了 GeoPolars 的工作,希望很快有更多消息。 如果您发现新版本任何问题,请提交 issue:https://github.com/pola-rs/polars/issues。最后,为协助升级至 2.0,我们发布了迁移指南。

基准测试附录

下方的绝对数值(单位:秒)中,加粗数字代表该行中最快的引擎;背景色越深,表示该引擎相较于最快引擎的速度越慢。Polars 在 32 线程配置下仅在 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 倍的提速。由于 Polars 32 线程配置仅在 c7a.metal 上运行,故未包含在此项对比中。

脚注

  1. 这些基准测试源自 TPC-H 和 TPC-DS 基准测试,因此所得结果与公开的 TPC-H 和 TPC-DS 基准测试结果不具备可比性,因为这些结果不符合 TPC-H 和 TPC-DS 基准测试的标准。 ↩ ↩2

原始来源: Hacker News

评论 (0)