对于以下示例,它涉及自条件连接和后续的 groupby/aggregate 操作。事实证明,在这种情况下,
DuckDB
的性能比 Polars
好得多(在 32 核机器上约为 10 倍)。
我的问题是:
DuckDB
缓慢(相对于 Polars
)的潜在原因可能是什么?Polars
中做同样事情的其他更快的方法?import time
import duckdb
import numpy as np
import polars as pl
## example dataframe
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),
)
)
## polars
start = time.perf_counter()
res = (
df.lazy()
.join(df.lazy(), on=["id", "id2"], how="left")
.filter(
(pl.col("id3") > pl.col("id3_right"))
& (pl.col("id3") - pl.col("id3_right") < 30)
)
.group_by(["id2", "id3", "id3_right"])
.agg(pl.corr("value", "value_right"))
.collect(streaming=True)
)
time.perf_counter() - start
# 120.93155245436355
## duckdb
start = time.perf_counter()
res2 = (
duckdb.sql(
"""
SELECT df.*, df2.id3 as id3_right, df2.value as value_right
FROM df JOIN df as df2
ON (df.id = df2.id
AND df.id2 = df2.id2
AND df.id3 > df2.id3
AND df.id3 - df2.id3 < 30)
"""
)
.aggregate(
"id2, id3, id3_right, corr(value, value_right) as value",
"id2, id3, id3_right",
)
.pl()
)
time.perf_counter() - start
# 18.472263277042657
最新发布的 Polars 已将差异从 15 倍缩小到 2 倍。
polars v0.18.2 1125
polars v0.18.3 140
duckdb 0.8.2-dev1 75
流媒体 API 尚未优化。 Polars 是一个比 DuckDB 更年轻的项目,我们没有那么多付费开发人员参与该项目。
所以请给我们时间。下一个版本
0.18.3
将发布 PR,可以使流媒体 groupby 速度提高 3.5 倍以上 https://github.com/pola-rs/polars/pull/9346.
这只是表明我们在流媒体引擎上还有多少空间。我们仍然需要对流连接进行同样的优化。
简而言之。我们的流媒体引擎正处于 alpha 阶段。该工作正在进行中。
另外,duckdb 查询也可能在底层使用非等值连接,而我们在 Polar 中还没有,所以这个查询对于 Polar 来说可能不是最佳的。
虽然 DuckDB 确实有几个非等值连接,但规划器当前假设所有等式谓词比不等式更具选择性,并且仅在此处生成哈希连接:
D EXPLAIN SELECT id2, id3, id3_right, corr(value, value_right) as value
> FROM (
> SELECT df.*, df2.id3 as id3_right, df2.value as value_right
> FROM df JOIN df as df2
> ON (df.id = df2.id
> AND df.id2 = df2.id2
> AND df.id3 > df2.id3
> AND df.id3 - df2.id3 < 30)
> ) tbl
> GROUP BY ALL
> ;
┌─────────────────────────────┐
│┌───────────────────────────┐│
││ Physical Plan ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│ HASH_GROUP_BY │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ #0 │
│ #1 │
│ #2 │
│ corr(#3, #4) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ id2 │
│ id3 │
│ id3_right │
│ value │
│ value_right │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PROJECTION │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ #1 │
│ #2 │
│ #3 │
│ #4 │
│ #5 │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ ((id3 - id3) < 30) │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ EC: 24727992087 │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ HASH_JOIN │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ INNER │
│ id = id │
│ id2 = id2 ├──────────────┐
│ id3 > id3 │ │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
│ EC: 24727992087 │ │
│ Cost: 24727992087 │ │
└─────────────┬─────────────┘ │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│ SEQ_SCAN ││ SEQ_SCAN │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ││ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ df ││ df │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ││ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ id ││ id │
│ id2 ││ id2 │
│ id3 ││ id3 │
│ value ││ value │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ││ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ EC: 5000000 ││ EC: 5000000 │
└───────────────────────────┘└───────────────────────────┘
我们计划在未来的版本中解决这个问题。
另请注意,IEJoin 算法需要两个不等式,而查询只有一个。单个不等式可以由 PieceWiseMergeJoin 运算符处理,但 PWMJ 目前不处理简单的等式(只需扩展逻辑即可正确处理
NULL
)。