用 Rust + Rayon 加速数据分析,性能提升 4 倍!

文摘   科技   2024-12-19 00:21   四川  

引言

在数据科学领域,Python 一直是最受欢迎的编程语言之一。但在处理大规模数据集时,Python 的性能往往会成为瓶颈。今天要介绍的 Rust 的 Rayon 库,不仅能让并行数据处理变得异常简单,而且性能相比 Python 提升了约 4 倍!

问题背景

假设我们需要分析一个包含 143,000 篇新闻文章的数据集,统计每篇文章中提到的男性和女性代词(he/him/his vs she/her/hers)的数量,来粗略估计文章中提到的性别比例。

这是一个典型的"尴尬并行"(embarrassingly parallel)问题 —— 每篇文章可以独立处理,非常适合进行并行计算。

Rayon 简介

Rayon 是 Rust 生态系统中用于数据并行处理的强大框架。它的核心优势包括:

  1. 使用简单:只需要将普通的迭代器 .iter() 替换为并行迭代器 .par_iter()
  2. 智能调度:自动决定是否需要并行执行以及如何分配工作
  3. 安全保证:得益于 Rust 的所有权系统,在编译时就能保证并行代码的安全性

代码实现对比

Python 版本

import re
from concurrent.futures import ProcessPoolExecutor

def clean_text(text: str) -> str:
    text_lower = text.lower()
    suffix_mapping = {
        "s"" is",
        "d"" had",
        "ll"" will",
    }
    # 替换缩写
    formatted_text = re.sub(r"([''])(s|d|ll)"
                          lambda x: suffix_mapping[x.group(2)], 
                          text_lower)
    # 移除非字母字符
    result = re.sub(r"[^a-zA-Z\s]""", formatted_text)
    return result

def count_gendered_pronouns(tokens: list[str]) -> tuple[int, int]:
    # 统计男性代词
    num_male_pronouns = sum(1 for token in tokens 
                          if token in ["he""him""his"])
    # 统计女性代词
    num_female_pronouns = sum(1 for token in tokens 
                            if token in ["she""her""hers"])
    return num_male_pronouns, num_female_pronouns

Rust + Rayon 版本

use rayon::prelude::*;
use regex::Regex;

fn clean_text(text: &str) -> String {
    let pattern1 = Regex::new(r"([''])(s|d|ll)").unwrap();
    // 替换缩写
    let matched = pattern1.replace_all(text, |capture: &Captures| match &capture[2] {
        "s" => " is",
        "d" => " had",
        "ll" => " will",
        _ => "<unk>",
    });
    // 移除非字母字符
    let pattern2 = Regex::new(r"[^a-zA-Z\s]").unwrap();
    let clean_text = pattern2.replace_all(&matched, "");
    clean_text.to_lowercase()
}

fn count_gendered_pronouns(tokens: Vec<&str>) -> (usizeusize) {
    // 使用并行迭代器统计代词
    let num_male_pronouns = tokens
        .par_iter()
        .filter(|&x| *x == "he" || *x == "him" || *x == "his")
        .count();
    let num_female_pronouns = tokens
        .par_iter()
        .filter(|&x| *x == "she" || *x == "her" || *x == "hers")
        .count();
    (num_male_pronouns, num_female_pronouns)
}

Rayon 的工作原理

  1. 并行迭代器:通过 par_iter 方法实现,内部使用分治策略将工作分配给多个线程。

  2. 工作窃取算法

  • 每个 CPU 核心启动一个工作线程
  • 空闲线程会主动"窃取"其他线程的未完成工作
  • 动态适应系统负载,自动调整并行度
  • 安全保证

    • Rust 的所有权系统确保没有数据竞争
    • 编译时检查避免运行时错误
    • 类型系统保证并行代码的正确性

    性能对比

    在处理同样的数据集时,Rust + Rayon 的实现比 Python 的多进程实现快了约 4 倍,同时代码更加简洁易读。这得益于:

    1. Rust 的零成本抽象
    2. Rayon 智能的工作调度
    3. 编译器优化
    4. 更少的内存开销

    总结

    Rayon 展示了 Rust 生态系统的强大之处:

    1. 简单易用的 API
    2. 出色的性能表现
    3. 可靠的安全保证
    4. 优秀的抽象设计

    对于数据科学工作者来说,Rust + Rayon 是一个值得投资的技术组合,特别适合处理计算密集型的数据分析任务。

    参考文章

    1. Speeding up data analysis with Rayon and Rust: https://thedataquarry.com/posts/intro-to-rayon/

    书籍推荐

    各位 Rust 爱好者,今天为大家介绍一本《Programming Rust: Fast, Safe Systems Development》(第二版) 是由 Jim Blandy、Jason Orendorff 和 Leonora Tindall 合著的 Rust 编程指南。本书深入探讨了 Rust 语言在系统编程中的应用,着重介绍如何利用 Rust 的独特特性来平衡性能和安全性。书中涵盖了 Rust 的基础数据类型、所有权和借用概念、特征和泛型、并发编程、闭包、迭代器以及异步编程等核心内容。这本更新版基于 Rust 2021 版本,为系统程序员提供了全面而实用的 Rust 编程指导。

    1.  Rust:横扫 C/C++/Go 的性能之王?

    2.  从 Rust 开发者视角看 C++:优缺点大揭秘

    3.  Rust vs Zig:新兴系统编程语言之争

    数据科学研习社
    带你走进数据科学的世界🚀
     最新文章