分析型數(shù)據(jù)庫(kù)DuckDB基準(zhǔn)測(cè)試
我們都知道Polars很快,但是最近DuckDB以其獨(dú)特的數(shù)據(jù)庫(kù)特性讓我們對(duì)他有了更多的關(guān)注,本文將對(duì)二者進(jìn)行基準(zhǔn)測(cè)試,評(píng)估它們的速度、效率和用戶友好性。
在評(píng)測(cè)之前我們先看看這兩個(gè)框架
- DuckDB(0.9.0):一個(gè)用c++編寫的內(nèi)存分析數(shù)據(jù)庫(kù)。
- Polars(0.19.6):一個(gè)用Rust實(shí)現(xiàn)的超快的DataFrame庫(kù)
除此以外還有Pandas、Dask、Spark和Vaex本文主要關(guān)注DuckDB和Polars的基準(zhǔn)測(cè)試,因?yàn)樗鼈兲貏e強(qiáng)調(diào)在某些環(huán)境下的速度性能。之所以對(duì)這兩個(gè)框架進(jìn)行對(duì)比是因?yàn)?Polars是我目前測(cè)試后得到最快的庫(kù),而DuckDB它可以更好的支持SQL,這對(duì)于我來說是非常好的特這個(gè),因?yàn)槲腋?xí)慣使用SQL來進(jìn)行查詢。
指標(biāo)設(shè)置
我使用了官方的polar基準(zhǔn)測(cè)試存儲(chǔ)庫(kù)進(jìn)行此評(píng)估?;鶞?zhǔn)測(cè)試由tpc標(biāo)準(zhǔn)化查詢組成。這些是專門用來評(píng)估實(shí)際的、真實(shí)的工作流的性能的。在Polars官方網(wǎng)站上,提供了8個(gè)此類查詢的詳細(xì)結(jié)果。這個(gè)基準(zhǔn)包含22個(gè)唯一的查詢(q1、q2等)。這些范圍從多表連接到聚合排序,所有這些都是大家認(rèn)可的經(jīng)過特殊構(gòu)建的查詢。
測(cè)試在一臺(tái)配備16核AMD vCPU和32GB RAM的機(jī)器上進(jìn)行。所有代碼都使用Python 3.10執(zhí)行。
數(shù)據(jù)大小
數(shù)據(jù)是由使用scale10的存儲(chǔ)庫(kù)代碼生成的,下面是每個(gè)實(shí)體的大小
數(shù)據(jù)轉(zhuǎn)換與查詢
我們文件讀取到內(nèi)存中,然后進(jìn)行查詢。
在q1、q9、q13和q17中,多連接、基于字符串的過濾和復(fù)雜聚合的組合對(duì)于polars 來說很難像duckdb那樣有效地進(jìn)行優(yōu)化。Q21是對(duì)惟一值的計(jì)數(shù)、基于這些計(jì)數(shù)進(jìn)行過濾以及隨后的一系列連接的操作。
總的來說DuckDB在這兩種情況下看起來更快,但這并不是全部。
因?yàn)閷?shù)據(jù)加載到內(nèi)存中的過程會(huì)產(chǎn)生時(shí)間和內(nèi)存開銷。我們通過Makefile準(zhǔn)確地度量這些成本。
/usr/bin/time -v make run_duckdb
/usr/bin/time -v make run_polars
與polar相比,DuckDB在直接讀取文件時(shí)表現(xiàn)出更快的性能和更低的內(nèi)存使用。這表明polars 可能使用了交換內(nèi)存(紅色)。這些庫(kù)不是為跨多臺(tái)機(jī)器擴(kuò)展而設(shè)計(jì)的,所以它們都進(jìn)行了高效CPU核心利用率的設(shè)計(jì)。
Polars在某些特定領(lǐng)域表現(xiàn)出具有競(jìng)爭(zhēng)力甚至更好的性能,例如直接讀取文件時(shí)的磁盤IO和內(nèi)存操作時(shí)的RAM IO。在磁盤IOPS較低的系統(tǒng)中,polar可以表現(xiàn)得更好。
另外:上圖中的CPU百分比越高越好。值大于100%表示正在使用多核處理。
下面是我們測(cè)試的代碼:
DuckDB讀取Parquet直接查詢
import duckdb
conn = duckdb.connect(database=':memory:')
df_count = conn.sql("""
SELECT
count(*) as count_order
FROM
'lineitem.parquet'
"""
).fetchdf()
print(df_count)
DuckDB內(nèi)存查詢
import duckdb
conn = duckdb.connect(database=':memory:')
conn.sql("""
CREATE TEMP TABLE IF NOT EXISTS lineitem AS
SELECT *
FROM read_parquet('lineitem.parquet');
"""
)
df_count = conn.sql("""
SELECT
count(*) as count_order
FROM
lineitem
"""
).fetchdf()
print(df_count)
Polars 讀取Parquet直接查詢
import polars as pl
df = pl.scan_parquet('lineitem.parquet')
df_count = df.select(
pl.count().alias("count_order"),
).collect()
print(df_count)
Polars 內(nèi)存查詢
import polars as pl
df = pl.scan_parquet('lineitem.parquet')
df = df.collect().rechunk().lazy()
df_count = df.select(
pl.count().alias("count_order"),
).collect()
print(df_count)
總結(jié)
可以看到在Python處理引擎領(lǐng)域,DuckDB是一個(gè)很有前途的競(jìng)爭(zhēng)者。他在涉及連接和復(fù)雜聚合的任務(wù)中表現(xiàn)非常亮眼。另外它的簡(jiǎn)單并且更干凈、而且還支持SQL語句直接查詢。
但是DuckDB仍處于初級(jí)階段??赡芘紶枙?huì)遇到bug或缺少功能的問題,如果你有興趣,可以在你的研究項(xiàng)目中使用DuckDB替代Polars或者Pandas。
本文的測(cè)試腳本:
https://github.com/pola-rs/tpch#polars-tpch