page contents

Pandas、Polars、DuckDB 百万行 CSV 基准测试性能对比

Pandas、Polars 和 DuckDB 都能在 Python 数据处理中承担分析任务,但三者的执行方式和资源占用差异很大。面对同一份大型 CSV,Pandas 的内存模型容易成为瓶颈;Polars 依靠多线程与惰性求值缩短处理时间;DuckDB 则把文件查询交给进程内 SQL 引擎,并支持超出内存容量的数据。

attachments-2026-11-RLK1byY56aed33182cd5d.png

Pandas、Polars 和 DuckDB 都能在 Python 数据处理中承担分析任务,但三者的执行方式和资源占用差异很大。面对同一份大型 CSV,Pandas 的内存模型容易成为瓶颈;Polars 依靠多线程与惰性求值缩短处理时间;DuckDB 则把文件查询交给进程内 SQL 引擎,并支持超出内存容量的数据。

本文使用同一份 2.3GB、约 120 万行的 CSV,在相同机器上依次测试这三种工具。测试覆盖文件读取、筛选、分组、聚合、连接和排序,并比较完整任务耗时、内存峰值与 CPU 利用情况。除了性能数据,后面也会说明各自的生态限制、适用范围以及混合 Pipeline 的分工方式。

测试结果的差距很大:Pandas 的内存峰值达到 7.2GB,Pipeline 直接崩溃了;Polars 在 8.7 秒内完成处理,内存峰值为 1.8GB;DuckDB 的完整查询耗时 12 毫秒,内存峰值只有 0.4GB。

测试设置:同一工作负载下的三组对比

三种工具处理同一份 2.3GB CSV,执行完全相同的操作:读取文件,筛选 revenue 高于 1000 的行,按 region 和 quarter 分组,对 revenue 求和并统计交易数量;随后与一张约有 50k 行的查找表连接,再按总 revenue 降序排列。

测试内容是常见的分析与聚合任务:这类工作本不该要求一个分布式系统。所以测试机器只是一台每月 40 美元的 DigitalOcean Droplet,配有 4 个 vCPU 和 8GB 内存。

Pandas

我真的喜欢 Pandas。数据科学的每个教程都从 import pandas as pd 开始。不过,Pandas 的问题是所有内容都会载入内存,包括每一行、每一列,以及每个作为 Python 对象存储并带有 49 字节开销的字符串。一个 2.3GB CSV 进入 Pandas 后不会继续保持 2.3GB。字符串变成对象,整数变成 int64;整数列里只要有一个缺失值,整列就成了 float64。

测试时,单是读取文件就花了 47 秒。groupby 开始后,内存使用量升至 6.8GB;merge 阶段达到 7.2GB;接着还要排序。服务器开始使用交换空间,然后OOM killer 。

我也试过优化:指定 dtypes,将字符串设为 category,把 float64 降成 float32。内存降到了约 4.5GB,任务最终在 89 秒内完成,但整套方案非常的脆弱,数值列中只要混入一个意外的字符串流程就会崩掉。

那么换成 10 亿行会怎样?Pandas 不只是变慢,而是直接死掉:内存耗尽、崩溃、结束,即便机器配有 64GB 内存也是如此。

Pandas 在出问题之前一直很好用,只不过数据量不能太大

Polars

Polars 用 Rust 编写,采用 Apache Arrow 列式格式(columnar format),默认启用多线程,还带有一个会在查询运行前执行优化的惰性求值(lazy evaluation)引擎。Pandas 没有的这些特性,它都有(最新版的pands也用 Arrow 列式格式了)。

同一台机器、同一个 CSV、同一套工作负载,Polars 在 3.2 秒内读完文件。读取、筛选、groupby、merge、排序这一整条 Pipeline 共耗时 8.7 秒,内存峰值为 1.8GB,比 Pandas 少 60%,而且没有崩溃。

关键在于惰性 API。代码写成这样:

importpolarsaspl  

result= (  
    pl.scan_csv("data.csv")  
    .filter(pl.col("revenue") >1000)  
    .group_by(["region", "quarter"])  
    .agg([  
        pl.col("revenue").sum(),  
        pl.col("transactions").count()  
    ])  
    .join(lookup, on="region_id")  
    .sort("revenue_sum", descending=True)  
    .collect()  
)

调用 .collect() 之前,Polars 不会执行任何操作。它先构建查询计划(query plan),做过滤下推(filter pushdown)并剔除未使用的列。若改用 .collect(engine="streaming"),数据还能分块流式处理。

H2O.ai 针对 1000 万行数据的基准测试显示,Polars 完成一次 groupby 需要 0.45 秒,Pandas 则要 12.5 秒,速度相差 28 倍。数据量达到 10 亿行后,前者可以在 45 秒内流式处理完毕;同一台 64GB 内存的机器上,后者会因内存不足而崩溃。

不过语法 API 要花点时间适应。原来的 df['column'].mean() 要改写成 pl.col('column').mean(),确实存在学习曲线。

Polars 就是 Pandas 本应进化成的样子:速度快、节省内存,也确实会用上所有 CPU 核心。

DuckDB

DuckDB 属于进程内分析数据库(in-process analytical database),并非 DataFrame 库。它直接运行在 Python 进程中,无需启动服务器,也没有连接字符串,import duckdb 后写 SQL 即可。

我起初有些怀疑。为一个 CSV 文件写 SQL,听起来就很诧异,不过查询跑起来后就不一样了

importduckdb  

result=duckdb.sql("""  
    SELECT   
        d.region,  
        d.quarter,  
        SUM(d.revenue) as revenue_sum,  
        COUNT(*) as transaction_count  
    FROM read_csv_auto('data.csv') d  
    JOIN lookup l ON d.region_id = l.region_id  
    WHERE d.revenue > 1000  
    GROUP BY d.region, d.quarter  
    ORDER BY revenue_sum DESC  
""").df()

不是 12 秒,是 12 毫秒。

再跑一次,11ms;又跑一次,13ms。我检查了代码,确认没有误查一张空表。

DuckDB这么快的原因在于 DuckDB 是查询引擎,而非 DataFrame 库。SQL 会编译成经过优化的执行计划,底层采用向量化执行(vectorized execution),只读取需要的列。Parquet 文件也能直接查询,不必载入内存;当查询所需空间超过内存时,数据会自动溢写到磁盘(spill to disk)。

TPC-H 基准测试中,DuckDB 在单台机器上用 1 分 16 秒完成了完整测试。Apache Spark 在 32 台机器组成的集群上花了约 8 分钟。即使将 Spark 扩展到多台机器上的 1,024 个核心,它仍赶不上 DuckDB 的单机性能。Pandas 和 Polars DataFrame 也能由 DuckDB 直接查询:

importpandasaspd  
importduckdb  
  
df=pd.read_csv("data.csv")  
result=duckdb.sql("SELECT * FROM df WHERE revenue > 1000").df()

它像是数据处理领域的一把瑞士军刀:想用 SQL 时就写 SQL,需要 DataFrame 时就用 DataFrame,两者之间切换没有任何开销。

DuckDB 不只是快,甚至快得令人难堪——快到让人觉得,以前用其他方式做这些事很可笑。

没有什么是完美的

Pandas 依旧拥有最成熟的生态系统:Scikit-learn、Matplotlib、Seaborn,还有网上数不清的教程。数据集能放进内存,需要做探索性数据分析(exploratory data analysis)时,它仍是合适的选择。Pandas 3.0 中的写时复制(Copy-on-Write,在它最终发布之后)也会改善内存安全性。

Polars 的生态系统较小。Scikit-learn 目前还不能原生接受 Polars DataFrame,表达式 API 能力丰富,却不太符合很多人的既有习惯。某些操作在 Pandas 中很容易,例如逐行 apply;Polars 有意让它们变得更难,因为这类操作违背列式模型。

DuckDB 以 SQL 为先,若讨厌 SQL那么使用起来不会轻松。它进入 Python 数据科学领域的时间也更晚。虽然DataFrame 集成已经做得不错,但还不如原生 Polars 那么无缝。遇到用代码比用 SQL 更容易表达的复杂转换,Polars 可能更顺手。

到底该用哪一个?

数据能轻松放进内存时——加载后小于 1GB——用 Pandas。需要 scikit-learn、statsmodels 等生态工具,或者只是快速做探索性分析,也选它。如果团队成员已经熟悉 Pandas,学习新 API 又不划算,同样没必要更换。

数据很大但仍能放进内存,即 1GB 到 100GB 时,可以用 Polars。它适合需要多线程性能、惰性求值和查询优化的场景,也适合生产环境中的 ETL Pipeline;内存效率是另一个选择理由。

想直接查询文件而不将其载入内存,可以选择 DuckDB。它适合偏好 SQL 或以 SQL 为主的团队,也能处理大于内存的数据,即核外处理(out-of-core processing)。如果追求最快的查询性能,工作负载又以分析为主而非逐行转换,DuckDB 更合适。

其实多数团队采用的是混合方案:DuckDB 负责数据摄取(data ingestion)和重型 SQL 分析,Polars 承担复杂转换与特征工程(feature engineering),Pandas 留给最后一公里的 ML、可视化和报告。

总结

Pandas 没有死,但它已不再是严肃数据工作的默认选择。

Pandas 适合小数据和生态兼容性,Polars 适合快速且节省内存的 DataFrame 操作,DuckDB 则擅长快得违背物理规律的 SQL 分析。

数据量不断增长可以试试 Polars;想不经加载便直接查询文件,可以试试 DuckDB;所有工作依然都用 Pandas 也没关系,只是需留意内存使用量。

 更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2560 篇文章

作家榜 »

  1. Pack 2560 文章
  2. 轩辕小不懂 2403 文章
  3. 小柒 2228 文章
  4. Nen 576 文章
  5. 王昭君 217 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章