You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python多进程场景下如何避免重复读取CSV并高效共享Polars DataFrame?

Python多进程场景下如何避免重复读取CSV并高效共享Polars DataFrame?

哈哈,我太懂你这种困惑了——本来想着提前把CSV读好,让多进程共享数据来省时间,结果反而比每个进程各自读还慢,简直反向优化了😂 其实这个问题的核心在于多进程间的数据传递机制,咱们一步步拆解原因,再给你几个可行的解决方案。

为什么直接传DataFrame会变慢?

你用的ProcessPoolExecutor在Windows/macOS系统下默认是用spawn模式启动子进程的(Linux默认是fork)。在spawn模式下,子进程会从头开始初始化Python解释器,然后把父进程中需要传递的对象(比如你的DataFrame)通过pickle序列化后传过去。

而Polars的DataFrame底层是基于Arrow格式的大内存块,序列化整个DataFrame的开销特别大——比每个进程单独读一次CSV的开销还要高,这就是为什么你觉得变慢了。

解决方案1:Unix系统下利用写时复制(COW)特性

如果你是在Linux或者macOS上跑代码,那可以利用Unix系统的写时复制机制来零开销共享DataFrame。

Unix的fork模式下,子进程会直接共享父进程的内存页,只有当子进程尝试修改内存内容时,才会复制对应的内存页(也就是“写时复制”)。而Polars的DataFrame是不可变对象,你的操作只是过滤生成新的DataFrame,不会修改原DataFrame,所以子进程完全不用复制原数据,直接共享父进程的内存即可。

代码示例(Python 3.10+支持):

import polars as pl
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

def process_data(df, numbers):
    # 你的处理逻辑,这里只是示例过滤
    return df.filter(pl.col("Points").is_in(numbers)) 

def worker(task_args):
    df, numbers = task_args
    return process_data(df, numbers)

if __name__ == "__main__":
    # 父进程中只读取一次CSV
    df = pl.read_csv("path/to/file.csv")

    # 用fork模式创建进程池,利用写时复制共享DataFrame
    mp_context = multiprocessing.get_context("fork")
    with ProcessPoolExecutor(max_workers=4, mp_context=mp_context) as executor:
        # 把DataFrame和每个任务的参数打包传递
        task_list = [(df, [10, 20]), (df, [30, 40]), (df, [50, 60]), (df, [70, 80])]
        results = list(executor.map(worker, task_list))

这个方案几乎没有额外开销,性能会比每个进程读CSV快很多。

解决方案2:跨平台通用方案——Arrow IPC内存映射

如果你的代码需要跨平台(比如要在Windows上跑),那可以用Arrow IPC格式+内存映射的方式来共享数据,这是目前最稳妥的跨平台方案。

Arrow IPC是专门为大数据场景设计的存储格式,支持内存映射读取——简单说就是每个进程不需要把整个数据加载到内存,而是直接“映射”磁盘上的Arrow文件到自己的内存空间,多个进程可以共享这个映射,开销极低。

代码示例:

import polars as pl
from concurrent.futures import ProcessPoolExecutor
import os

def process_data(df, numbers):
    return df.filter(pl.col("Points").is_in(numbers)) 

def worker(numbers):
    # 用内存映射读取Arrow文件,几乎无额外开销
    df = pl.read_ipc("temp_data.arrow", memory_map=True)
    return process_data(df, numbers)

if __name__ == "__main__":
    # 父进程中读取CSV并保存为Arrow IPC临时文件
    df = pl.read_csv("path/to/file.csv")
    df.write_ipc("temp_data.arrow")

    try:
        with ProcessPoolExecutor(max_workers=4) as executor:
            task_params = [[10,20], [30,40], [50,60], [70,80]]
            results = list(executor.map(worker, task_params))
    finally:
        # 任务完成后删除临时文件
        os.remove("temp_data.arrow")

这个方案跨平台通用,性能比重复读CSV好很多,也避免了序列化整个DataFrame的巨大开销。

额外小提醒:别用pickle传Polars DataFrame

记住,在spawn模式下,绝对不要直接把Polars DataFrame传给子进程——pickle序列化大DataFrame的开销真的会让你怀疑人生。上面的两种方案都是绕开了直接传递DataFrame,要么用内存共享,要么用磁盘映射,这才是高效的正确姿势。

备注:内容来源于stack exchange,提问作者Pedro_Siqueira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 14:43:03