polars 处理千万级数据的性能关键在于全程保持 rust 执行:必须用 .lazy() 和 pl.scan_*,手动指定 dtypes,禁用 .apply(),pytorch 训练直连 polarsdataset;任何 python 回调都会使性能断崖式下跌。

Polars 在 Python 3.11 中处理千万级数据,根本不是靠 Python 版本提速,而是靠绕过 Python。你升级到 3.11 后感觉“快了一点”,大概率只是 import polars 更快、错误回溯更准,或者用 tomllib 解析配置更快——真正耗时的计算(读 Parquet、filter、groupby、agg)压根没走 Python 字节码。
所以别纠结“3.11 是否必须”,重点是:怎么让 Polars 少走弯路、多用 Rust、不掉回 Python 回调。
必须加 .lazy(),否则性能优势直接打五折
LazyFrame 不是可选项,是千万行以上的默认执行模式。Eager 模式下每步都立即分配内存、触发计算,中间结果全留在 RAM 里;Lazy 模式只建图,collect 前不做任何实际计算。
不加 lazy:
df = pl.read_csv("data.csv"); result = df.filter(...).groupby(...).agg(...)
→ 内存峰值翻倍,CPU 利用率常卡在 1–2 核加 lazy:
df = pl.scan_csv("data.csv"); result = df.filter(...).groupby(...).agg(...).collect()
→ Polars 自动做谓词下推(提前过滤)、投影裁剪(只读用到的列)、并行哈希分组-
容易踩的坑:
Python Matplotlib Chinese Font下载使用 font_manager.addfont() 添加中文字体文件,设置 rcParams['font.family'],并禁用 unicode_minus,使 matplotlib 显示中文。
-
pl.read_csv().lazy()是错的写法,应直接用pl.scan_csv()(后者专为懒执行设计,跳过 schema 推断开销) -
.collect()前别穿插.head()或.describe(),它们会强制触发部分执行,破坏优化链 - 如果要调试查询计划,用
.explain(optimized=True),别靠 print 看中间 DataFrame
-
读取阶段就锁死 dtypes,别信 infer_schema_length
千万级 CSV 默认推断类型极其耗时,且容易推错(比如把 ID 列当 float,再转 int 丢精度)。Polars 的 infer_schema_length=10000 并不省事,它只是“多看一万行”,仍可能误判。
正确做法:手动传
dtypes字典pl.scan_csv("orders.csv", dtypes={"user_id": pl.UInt32, "amount": pl.Float32, "status": pl.Categorical})-
为什么重要:
-
pl.UInt32比默认pl.Int64节省 50% 内存 -
pl.Categorical对低基数字符串列(如 status、region)压缩率达 80%+ -
pl.Float32在精度允许时比Float64快 1.8 倍、省内存一半
-
-
容易踩的坑:
- 用
pl.read_parquet()代替 CSV —— Parquet 天然带 schema,零推断开销,读取快 5–10 倍 - 如果必须读 CSV 且列太多,先用小样本跑一次
pl.read_csv("sample.csv").schema提取真实类型,再复用到全量
- 用
别用 .apply(),尤其别在字符串/嵌套 JSON 上用
.apply() 是 Polars 的性能断崖。它会把整列数据转成 Python 对象,逐元素调用你的函数,彻底退出向量化执行,CPU 回到单线程,还触发 GIL。
错误示例:
df.with_columns(pl.col("meta_json").apply(lambda x: json.loads(x).get("device")))
→ 千万行要跑几分钟,且极易 OOM-
正确替代:
- JSON 字段:用
pl.col("meta_json").str.json_path_match("$.device")(向量化解析) - 字符串提取:用
pl.col("url").str.extract(r"/product/(\d+)", 1),不是.str.contains().apply(...) - 复杂逻辑:拆成多个原生表达式组合,或用
pl.map_batches()(仍走 Arrow 批处理,不进 Python)
- JSON 字段:用
-
容易踩的坑:
-
.map_elements()和.apply()是同级危险操作,一律规避 - 如果真要 Python 回调(极少数场景),确保输入是
Series而非Expr,并设return_dtype避免自动推断拖慢 collect
-
PyTorch 训练前别转 NumPy,用 PolarsDataset 直通 Tensor
很多人做完 Polars 清洗后习惯性写 df.to_numpy() 或 df.to_pandas().values,再塞给 PyTorch DataLoader——这等于把 Arrow 零拷贝优势全扔了,额外多一次内存复制和类型转换。
正确路径:
from polars.ml.torch import PolarsDatasetdataset = PolarsDataset(df.select(pl.exclude("label")), df["label"])dataloader = DataLoader(dataset, batch_size=8192)-
为什么快:
-
PolarsDataset内部通过 Arrow IPC 映射,Tensor 数据直接从 Polars 内存页读取,无拷贝 - 支持
batch_size自动切片,不触发 collect 全量加载 - label 列支持任意 dtype(包括
pl.Categorical),自动转为 torch.long
-
-
容易踩的坑:
- 必须安装完整版:
pip install "polars[ml]",只装polars会报ModuleNotFoundError - 输入 DataFrame 不能含 list/dict 类型列(Arrow 不支持),需提前用
.list.eval()展平或丢弃 - 如果下游必须用 scikit-learn,再考虑
.to_numpy(),但优先查它是否支持 Arrow 数组(新版已支持)
- 必须安装完整版:
最常被忽略的一点:Polars 的快,90% 取决于你有没有让它“保持在 Rust 里”。一旦出现 apply、to_pandas、iter_rows、map_elements 这类词,就等于主动交出控制权——后面再怎么升 Python 版本也救不回来。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










