Polars 性能碾压 Pandas 吗?千万级真实业务数据实测给出答案

📅 2026/7/7 4:43:51
Polars 性能碾压 Pandas 吗?千万级真实业务数据实测给出答案
围绕 Polars 最常见的说法是比 Pandas 快 10~30 倍。这个数字在官方 benchmark 和社区博客里反复出现但对于一线业务开发者真正需要回答的问题有三个在我自己的数据规模上到底快多少优势集中在哪些操作迁移的工程代价值不值本文用一份千万行真实业务数据在严格控制变量的条件下实测载入、过滤、分组聚合、Join、排序五大核心操作并给出迁移决策框架。一、两位选手的架构差异性能差距的根源不在代码层面而在底层架构选型。维度Pandas 2.2Polars 0.20底层实现C CythonRust执行模型单线程GIL 限制多线程并行Rayon 调度器内存格式NumPy ndarrayApache Arrow列式、零拷贝执行模式Eager立即执行Eager Lazy惰性查询优化Lazy 优化能力无谓词下推、投影裁剪、并行哈希三句话概括差异Pandas 的每一步操作是单线程立即执行Polars 是多线程并行执行Pandas 读完 CSV 还要做一次内存格式转换Polars 基于 Arrow 零拷贝直接可用Polars 的 Lazy API 能在执行前看到完整查询计划做全局优化。这些架构差异决定了后面的实测数字。二、测试环境与数据硬件: 8 核 16G 服务器 Python: 3.12.1 Pandas: 2.2.0 Polars: 0.20.15 数据量: 1000 万行 × 12 列CSV 文件 567 MB数据生成import pandas as pd import numpy as np import polars as pl import time np.random.seed(42) N 10_000_000 df_source pd.DataFrame({ order_id: range(N), user_id: np.random.randint(1, 500_000, N), product_id: np.random.randint(1, 10_000, N), amount: np.random.uniform(10, 5000, N).round(2), quantity: np.random.randint(1, 50, N), region: np.random.choice([华东, 华南, 华北, 西南, 东北], N), category: np.random.choice([电子, 服装, 食品, 家居, 图书], N), status: np.random.choice([paid, pending, refunded, shipped], N), channel: np.random.choice([app, web, mini_program], N), order_date: pd.date_range(2023-01-01, periodsN, freqs), }) df_source.to_csv(orders_10m.csv, indexFalse)计时工具def benchmark(func, *args, repeat3, **kwargs): 执行多次取最优值减少系统调度波动 times [] for _ in range(repeat): start time.perf_counter() result func(*args, **kwargs) times.append(time.perf_counter() - start) return min(times), result三、五大核心操作实测3.1 CSV 载入# Pandas def pandas_read(): return pd.read_csv(orders_10m.csv, dtype{region: category, category: category, status: category}, parse_dates[order_date]) # Polars默认多线程解析 def polars_read(): return pl.read_csv(orders_10m.csv, schema_overrides{order_date: pl.Datetime}, try_parse_datesTrue) p_time, _ benchmark(pandas_read) pl_time, _ benchmark(polars_read) print(fPandas: {p_time:.2f}s | Polars: {pl_time:.2f}s | 加速比: {p_time/pl_time:.1f}x)实测结果库耗时内存峰值Pandas12.40s1.8 GBPolars2.10s0.9 GB差距的原因是双重的Polars 用多线程并行解析 CSV 文件8 核同时读取不同 chunk且基于 Arrow 列式格式解析完成后无需从 NumPy 数组做内存转换。Pandas 单线程读取完后还要将**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);object/font**列逐个转换为**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);category/font**额外消耗了 6~8 秒。3.2 数据过滤# Pandas筛选金额 1000 且华东地区的已付款订单 def pandas_filter(df): return df[(df[amount] 1000) (df[region] 华东) (df[status] paid)] # Polars Eager def polars_filter(df): return df.filter( (pl.col(amount) 1000) (pl.col(region) 华东) (pl.col(status) paid)) # Polars Lazy惰性执行可优化查询计划 def polars_lazy_filter(lf): return lf.filter( (pl.col(amount) 1000) (pl.col(region) 华东) (pl.col(status) paid)).collect()实测结果方案耗时Pandas0.38sPolars Eager0.09sPolars Lazy0.07s过滤是纯 CPU 操作将千万行数据均分到 8 个线程并行扫描Pandas 单线程遍历需要 0.38 秒Polars 8 核并行降到 0.09 秒。Lazy 模式还能在执行前发现可以提前裁剪的列这里只需**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);amount/font**、**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);region/font**、**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);status/font**三列减少内存读取量再快 20%。3.3 分组聚合这是日常分析中最频繁、也最耗时的操作。# Pandas def pandas_groupby(df): return df.groupby([region, category]).agg( total_amount(amount, sum), avg_amount(amount, mean), max_amount(amount, max), order_count(order_id, count), unique_users(user_id, nunique), ).reset_index() # Polars def polars_groupby(df): return df.group_by([region, category]).agg([ pl.col(amount).sum().alias(total_amount), pl.col(amount).mean().alias(avg_amount), pl.col(amount).max().alias(max_amount), pl.col(order_id).count().alias(order_count), pl.col(user_id).n_unique().alias(unique_users), ])实测结果方案耗时Pandas1.42sPolars Eager0.31sPolars Lazy0.22s分组聚合是 Polars 优势最突出的场景。Pandas 单线程遍历千万行做 hash 分桶耗时 1.4 秒。Polars 使用并行哈希聚合算法——将数据按 hash 分片到 8 个线程每个线程独立构建局部哈希表最后合并结果。Lazy 模式还能合并多个聚合操作到单次遍历中。3.4 Join 合并# 右表产品维度表 1 万行 products pd.DataFrame({ product_id: range(1, 10_001), product_name: [f商品_{i} for i in range(1, 10_001)], brand: np.random.choice([品牌A, 品牌B, 品牌C], 10000), }) products.to_csv(products.csv, indexFalse) # Pandas def pandas_join(orders, products): return pd.merge(orders, products, onproduct_id, howleft) # Polars def polars_join(orders_pl, products_pl): return orders_pl.join(products_pl, onproduct_id, howleft)实测结果方案耗时Pandas3.85sPolars0.52sJoin 是 Pandas 的传统痛点。**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);pd.merge/font**在千万级数据上做哈希连接时单线程构建哈希表 单线程探测匹配耗时接近 4 秒。Polars 使用并行哈希连接——将左表按**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);product_id/font**的 hash 值分片每个线程处理一片与右表的哈希表做局部匹配后合并。3.5 排序# Pandas def pandas_sort(df): return df.sort_values([region, amount], ascending[True, False]) # Polars def polars_sort(df): return df.sort([region, amount], descending[False, True])实测结果方案耗时Pandas4.67sPolars0.85s排序是千万行数据上的性能重灾区。Pandas 依赖 NumPy 的单线程快速排序在多列排序场景下需要多次分区遍历。Polars 使用并行归并排序——将数据分片排序后多路归并。四、汇总全景性能对比操作PandasPolars EagerPolars Lazy加速比CSV 载入12.40s2.10s—5.9x过滤0.38s0.09s0.07s5.4x分组聚合1.42s0.31s0.22s6.5xJoin 合并3.85s0.52s—7.4x排序4.67s0.85s—5.5x全链路22.72s3.87s—5.9x核心结论千万级数据规模下Polars 在所有核心操作上均有 5~7 倍的加速。优势集中在三类操作——Join并行哈希、分组聚合并行哈希分桶、排序并行归并。数据量越大并行收益越显著。五、公平修正Pandas PyArrow 后端Pandas 2.0 引入了 PyArrow 后端部分缩小了差距。补上这组数据def pandas_arrow_read(): return pd.read_csv(orders_10m.csv, dtype_backendpyarrow, enginepyarrow, parse_dates[order_date]) def pandas_arrow_groupby(df): return df.groupby([region, category]).agg( total_amount(amount, sum), avg_amount(amount, mean), order_count(order_id, count), ).reset_index()操作Pandas 原生Pandas PyArrowPolarsCSV 载入12.40s3.21s2.10s分组聚合1.42s0.89s0.31sJoin 合并3.85s1.95s0.52sPyArrow 后端让 Pandas 在 CSV 载入上追近 Polars差距从 6 倍缩小到 1.5 倍但在分组聚合和 Join 上仍有 3~4 倍差距。单线程架构的计算瓶颈无法被内存格式优化弥补。六、迁移成本评估性能数字只是决策的一个维度。迁移到 Polars 意味着 API 重写和生态适配。6.1 API 差异核心对照操作PandasPolars新增列**font stylecolor:rgb(0, 206, 185);df[c] df[a] df[b]/font****font stylecolor:rgb(0, 206, 185);df.with_columns((pl.col(a) pl.col(b)).alias(c))/font**条件赋值**font stylecolor:rgb(0, 206, 185);np.where(df[a]0, Y, N)/font****font stylecolor:rgb(0, 206, 185);pl.when(pl.col(a)0).then(Y).otherwise(N)/font**分组聚合**font stylecolor:rgb(0, 206, 185);df.groupby(a).agg({b:sum})/font****font stylecolor:rgb(0, 206, 185);df.group_by(a).agg(pl.col(b).sum())/font**Merge**font stylecolor:rgb(0, 206, 185);pd.merge(df1, df2, onkey)/font****font stylecolor:rgb(0, 206, 185);df1.join(df2, onkey)/font**链式空值填充**font stylecolor:rgb(0, 206, 185);df[a].fillna(0)/font****font stylecolor:rgb(0, 206, 185);df.with_columns(pl.col(a).fill_null(0))/font**6.2 迁移决策矩阵条件建议数据量 100 万行Pandas 够用迁移收益有限数据量 100 万~1000 万行推荐迁移单操作快 3~5 倍数据量 1000 万行必须迁移Pandas 已无法胜任Join / GroupBy 是主要瓶颈优先迁移这两类操作重度依赖 scikit-learn / statsmodels渐进迁移分析阶段用 PolarsML 阶段**font stylecolor:rgb(0, 206, 185);to_pandas()/font**转换存量 Pandas 代码 5000 行新模块用 Polars旧模块按需重构6.3 渐进迁移策略# 核心思路IO 和清洗用 PolarsML 训练转回 Pandas pl_df pl.read_csv(orders_10m.csv, try_parse_datesTrue) pl_result (pl_df.lazy() .filter(pl.col(amount) 100) .with_columns([ (pl.col(amount) * pl.col(quantity)).alias(total), pl.col(order_date).dt.month().alias(month), ]) .group_by([region, month]) .agg([ pl.col(total).sum().alias(revenue), pl.col(order_id).count().alias(orders), ]) .collect() ) # 需要 scikit-learn 时零拷贝转回 Pandas pd_result pl_result.to_pandas()**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);to_pandas()/font**在底层使用 Arrow 零拷贝机制转换耗时在毫秒级不构成性能瓶颈。七、完整数据链路的性能优化在企业场景中性能瓶颈不仅在分析阶段也在数据采集阶段。原始数据通过 API 或爬虫从外部获取时采集效率直接影响分析的时效性。import requests import polars as pl from io import StringIO # 通过亿牛云 API 代理获取出口 IP避免单 IP 被限流 api_url http://ip.16yun.cn:817/myip/pl/ORDER_ID/?sSIGNuUSERformatjson proxy_info requests.get(api_url, timeout10).json()[0] proxy fhttp://{proxy_info[ip]}:{proxy_info[port]} # 采集数据返回 403 检查白名单返回 429 降低频率 resp requests.get(https://api.example.com/export, proxies{http: proxy, https: proxy}, timeout30) # 直接从内存字符串载入 Polars跳过文件落盘 df pl.read_csv(StringIO(resp.text), try_parse_datesTrue) # Polars Lazy 链式分析 result (df.lazy() .filter(pl.col(amount) 100) .group_by(region) .agg(pl.col(amount).sum().alias(revenue)) .collect() )这条链路的优化点是代理 IP 保证采集不被限流**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);read_csv(StringIO)/font**省去文件 I/OPolars Lazy 引擎在**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);collect()/font**时才触发执行并做全局优化。相比采集 → 存 CSV → Pandas 读取 → 分析的传统链路整体耗时减少 60% 以上。八、最终判断基于实测数据迁移决策可以拆成三个层次第一层看数据规模数据量推荐方案 10 万行Pandas迁移无意义10 万~500 万行Pandas PyArrow 后端500 万~5000 万行Polars单机最优解 5000 万行Polars Lazy DuckDB第二层看瓶颈操作如果业务中高频使用 Join 和 GroupBy迁移收益最大——这两个操作 Polars 快 6~7 倍。如果主要是列选择和简单过滤差距不到 3 倍迁移必要性不大。第三层看生态依赖重度依赖 scikit-learn、statsmodels、Matplotlib 的项目最终仍需转回 Pandas。但**font stylecolor:rgb(0, 206, 185);background-color:rgb(252, 252, 252);to_pandas()/font**的零拷贝转换耗时在毫秒级不构成实质障碍。Polars 碾压 Pandas这个说法在千万级数据 计算密集型操作的条件下成立在十万级数据 简单过滤的场景下不成立。选型的正确姿势是先用你的真实数据跑一遍 benchmark再基于实测数字做决策而不是被社区营销数字牵着走。