找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2045

积分

0

好友

291

主题
发表于 2025-12-31 10:10:02 | 查看: 23| 回复: 0

用 Rust Polars 替代 Python pandas 处理数据,实测性能提升 20 倍。不是换电脑,只是换了个库。

为什么 Python 跑数据会慢?

首先需要明确,Python 本身并非不好。它开发速度快、易于上手、生态丰富,是数据科学领域的主力。但其设计初衷并非追求极致运行速度。

我们可以把 Python pandas 想象成一个勤恳的厨师,每道菜都亲力亲为:切菜、炒菜、装盘,严格按照顺序来。问题在于,当餐厅高峰期需要一小时出500道菜时,一个人慢慢炒就会导致整个后厨堵塞。具体来说,pandas 存在几个明显的性能瓶颈:

  1. 解释执行:Python 代码是逐行解释执行的,而非预先编译,这导致了固有的效率损失。
  2. 对象开销:pandas 的每个单元格都是一个独立的 Python 对象,大量的内存和 CPU 周期都消耗在处理这些“包装盒”上。
  3. 单核限制:默认情况下,pandas 仅使用单个 CPU 核心进行计算,其他核心处于闲置状态。
  4. 内存不友好:数据在内存中的布局可能较为分散,无法充分利用 CPU 的高速缓存。

而 Polars 采用 Rust 编写,作为编译型语言,天生具有速度优势。更重要的是,它采用了截然不同的数据处理架构。

Polars 为什么能快 20 倍?

沿用厨房的比喻,Polars 不是一个人在战斗,而是一条高效的流水线——切菜、炒菜、装盘等工序可以并行。每个环节都配备了专业工具:工业级切菜机、商用灶台、自动装盘器。从技术层面看,Polars 的成功得益于以下几点:

1. 列式存储

传统的 pandas 采用“行式存储”,类似于 Excel 表格,一行就是一条完整记录。Polars 则使用“列式存储”,将同一列的数据连续地存放在内存中。

列式存储与行式存储原理对比
图:列式存储(左)与行式存储(右)的内存布局示意图

这种方式为何更快?因为现代 CPU 极其擅长处理连续的内存块。就像阅读一本书,连续读完100页远比在不同页码间来回翻找100次要快得多。对数据分析中常见的聚合操作(如求和、求平均值),列式存储能极大减少数据访问量。

2. 懒执行 + 查询优化

Polars 提供“懒评估”(lazy)模式。它不会立即执行你的每一个指令,而是先记录下整个操作链,形成一个执行计划,并进行全局优化,最后一次性高效执行。

例如,你需要:1) 筛选出国家为“中国”的数据;2) 计算价格乘以数量得到新列;3) 按日期分组求和。pandas 会按顺序一步步执行。而 Polars 的优化器会分析整个计划,可能会决定在读取数据时就过滤掉无关行,并且只加载需要的列,避免了大量不必要的数据移动和计算。

3. 默认并行

Polars 天生支持多线程。如果你的电脑拥有8个CPU核心,它就能调动8个“工人”同时处理任务。相比之下,pandas 默认模式下只有一个核心在工作,其他核心则在“围观”。

4. SIMD 向量化

这是一个更底层的优化。简单来说,现代 CPU 支持 SIMD(单指令多数据流)指令集,可以一次性对一组数据(而非单个数据)进行相同操作。Polars 的设计充分挖掘了这一硬件潜力,实现了真正的向量化计算。

实测数据:性能差距显著

理论再好,不如实际测试。以下为本次的测试环境与结果:

  • 测试环境:8核 CPU,32GB 内存,NVMe 固态硬盘。
  • 测试数据:1000 万行,包含整数、浮点数、字符串等12列。
  • 处理任务:数据筛选、计算衍生列、多列分组聚合,并输出为 Parquet 文件。

结果对比如下:

方案 耗时 性能提升倍数
Python pandas 2.x 14.2 秒 1x (基准)
pandas + pyarrow 后端 10.7 秒 1.3x
Rust Polars 0.71 秒 20x

20倍的提升,意味着效率的质变。假设每天需要运行100次此类任务,使用 pandas 将累计耗时近24分钟,而 Polars 仅需约1分钟。节省下来的时间相当可观。

代码对比:简洁与高效并存

来看看 Polars 的 Rust 代码是如何实现的,其表达力与 pandas 相近,但底层效率天差地别:

use polars::prelude::*;

fn main() -> PolarsResult<()> {
    // 读取 CSV 文件
    let df = CsvReadOptions::default()
        .with_has_header(true)
        .try_into_reader_with_file_path(Some("events.csv".into()))?
        .finish()?;

    // 数据处理:筛选、计算、分组、聚合
    let result = df
        .lazy()  // 开启懒执行模式
        .filter(col("country").eq(lit("CN")))  // 筛选中国数据
        .with_column((col("price") * col("qty")).alias("amount"))  // 计算金额
        .group_by([col("day"), col("channel"), col("category")])  // 三列分组
        .agg([
            col("amount").sum().alias("revenue"),  // 求和
            col("id").count().alias("orders"),     // 计数
        ])
        .sort(["revenue"], SortMultipleOptions::default().with_order_descending(true))  // 排序
        .collect()?;  // 执行优化后的计划

    // 写入 Parquet 文件
    ParquetWriter::new(std::fs::File::create("output.parquet")?)
        .with_compression(ParquetCompression::Zstd(None))
        .finish(&mut result.clone())?;

    Ok(())
}

作为对比,以下是实现相同功能的 Python pandas 代码:

import pandas as pd

df = pd.read_csv("events.csv")
df = df[df["country"] == "CN"].copy()
df["amount"] = df["price"] * df["qty"]

result = (
    df.groupby(["day", "channel", "category"], as_index=False)
      .agg(revenue=("amount", "sum"), orders=("id", "count"))
      .sort_values("revenue", ascending=False)
)

result.to_parquet("output.parquet", compression="zstd")

两段代码的逻辑清晰度和代码量相差无几,但执行时间却一个是14秒,另一个仅为0.7秒。

适用场景与迁移建议

当然,Polars 并非万能钥匙,选择合适的工具很重要。

适合使用 Polars 的场景:

  • 数据量庞大(数百万、数千万行以上)。
  • 需要运行批处理任务或对执行时间有严格要求的流水线。
  • 追求极致的性能表现,愿意为效率投入学习成本。

Pandas 仍是合适选择的场景:

  • 进行探索性数据分析(EDA),数据量较小。
  • 重度依赖 pandas 特定生态库(如某些统计、绘图库)。
  • 团队技术栈以 Python 为主,引入 Rust 的学习成本过高。

补充一点:Polars 也提供了 Python 版本(py-polars)。虽然性能不及原生 Rust 版本,但仍显著快于 pandas,是 Python 用户一个不错的折中升级方案。

如果你打算迁移,建议采取渐进式策略:

  1. 试点先行:挑选一个最耗时的 pandas 任务进行迁移测试。
  2. 结果验证:确保 Polars 和 pandas 在处理相同输入时,输出结果完全一致。
  3. 并行运行:在一段时间内让两套逻辑并行,通过开关控制,验证稳定性。
  4. 全面切换:确认无误后,再正式替换核心流程。

迁移过程中,重点关注执行时间、CPU利用率、内存峰值和输出文件一致性这四个核心指标。

总结

总而言之,Rust Polars 能实现相比 Python pandas 数十倍的性能飞跃,其核心在于列式存储、查询优化和多线程并行等现代数据处理理念的深入应用。对于大规模数据处理任务,它提供了极具吸引力的解决方案。如果你正在为某个缓慢的数据管道而烦恼,尝试一下 Polars,或许就能体验到“代码运行完毕,咖啡尚且温热”的高效感。

探索更多数据处理与性能优化技巧,欢迎关注云栈社区的技术讨论。




上一篇:清华开源TurboDiffusion加速框架:视频生成提升200倍,迈入秒级时代
下一篇:彻底告别java.util.Date:现代Java开发如何选择日期时间API
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 09:07 , Processed in 0.258075 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表