You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Polars在条件自连接+分组聚合场景性能落后DuckDB的原因及优化方案

问题解答

一、Polars性能较差的潜在原因

  • 中间数据量差异:你当前的Polars代码先基于id和id2做全量左连接,生成的中间结果包含所有匹配(id,id2)对的id3组合,行数远超实际需要;而DuckDB将id3的过滤条件直接嵌入JOIN的ON子句,连接过程中就过滤掉无效行,中间数据集大小大幅降低,后续聚合的计算量也随之减少。
  • 查询优化能力差异:DuckDB作为OLAP数据库,其查询优化器对条件连接+聚合的场景有深度优化,能自动完成谓词下推、执行计划调优;而Polars的优化器未自动将过滤条件下推到连接阶段,导致无效数据的生成和处理。
  • 执行引擎适配性:DuckDB的引擎在复杂连接、聚合场景下的多核调度和内存管理更适配OLAP workload,并行执行效率更高。

二、Polars中更高效的实现方式

核心优化思路是将过滤条件下推到连接阶段,避免生成冗余中间数据,同时调整执行参数减少额外开销。优化后的代码如下:

import time
import numpy as np
import polars as pl

rng = np.random.default_rng(1)
nrows = 5_000_000
df = pl.DataFrame(
    dict(
        id=rng.integers(1, 1_000, nrows),
        id2=rng.integers(1, 10, nrows),
        id3=rng.integers(1, 500, nrows),
        value=rng.normal(0, 1, nrows),
    )
)

start = time.perf_counter()
res_optimized = (
    df.lazy()
    .join(
        df.lazy(),
        on=["id", "id2"],
        how="left",
        # 将id3的过滤条件作为连接条件的一部分,提前过滤无效行
        condition=(pl.col("id3") > pl.col("id3_right")) & (pl.col("id3") - pl.col("id3_right") < 30)
    )
    .drop_nulls()  # 过滤左连接后产生的无效null行
    .group_by(["id2", "id3", "id3_right"])
    .agg(pl.corr("value", "value_right"))
    .collect()  # 内存足够时关闭streaming,减少批次处理开销
)
print(time.perf_counter() - start)

关键优化点说明

  • 连接阶段过滤:通过condition参数在连接时就应用id3的过滤逻辑,从源头上减少中间数据量,这是性能提升的核心。
  • 关闭不必要的流式处理:streaming=True适合内存无法容纳全量数据的场景,但会增加调度和IO开销;在32核机器上处理500万行数据,内存通常足够,关闭流式处理能显著提升速度。
  • 过滤无效null行:左连接后不符合条件的行会生成null值,drop_nulls()能快速剔除这些行,减少后续聚合的计算量。

内容的提问来源于stack exchange,提问作者lebesgue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 01:28:08