PyArrow直接读Parquet比pandas快,因绕过DataFrame转换、可指定列/过滤/禁用类型推断、复用内存池、优化文件布局及合理并行;关键在读写协同调优。
PyArrow读取Parquet时为什么比pandas慢?
默认用 pandas.read_parquet() 且后端是 pyarrow,其实底层调的仍是 pyarrow.parquet.read_table(),但多了一层DataFrame转换开销。真正提速的关键不是换库,而是绕过pandas、直接用 pyarrow.parquet.read_table() 控制读取粒度。
常见错误是只改后端不改接口:比如写 pandas.read_parquet("x.parq", engine="pyarrow") 却没指定 columns 或 filters,结果全列全行加载,I/O和内存都浪费。
- 只读需要的列:
read_table("x.parq", columns=["user_id", "event_time"]) - 跳过不匹配的row group:
filters=[("status", "==", "success")](要求文件按该列排序或有统计信息) - 禁用自动类型推断(尤其字符串列):
use_pandas_metadata=False,避免解析嵌套schema
如何复用PyArrow内存池减少GC压力
高频小批量读取(如ETL流水线)中,反复创建/销毁 pyarrow.Table 会触发Python GC,同时PyArrow内部也频繁分配内存。启用内存池可显著降低延迟波动。
PyArrow 12+ 默认使用系统内存池,但旧版本需显式设置;若用jemalloc或mimalloc,效果更明显。
- 强制启用内存池:
pa.default_memory_pool(pa.jemalloc_memory_pool())(需提前安装jemalloc) - 读取时绑定池:
read_table(..., memory_pool=pa.default_memory_pool()) - 注意:多线程下各线程应共享同一池,不要每个线程新建一个
Parquet文件本身结构对PyArrow读取速度的影响
PyArrow能否跳过row group、是否能向量化解码,高度依赖Parquet文件的物理布局。同一份数据,不同写法生成的文件,读取性能可能差3倍以上。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
关键控制点在写入侧(哪怕你只负责读):
- row_group_size 推荐 64MB–128MB:太小则元数据膨胀、seek变多;太大则无法跳过无效数据
- 启用字典编码(
dictionary=True)对高基数字符串无效,反而拖慢;低基数(如状态码、分类标签)才收益明显 - 按查询高频字段排序并写入(
sort_by="timestamp"),让filters能利用min/max统计快速裁剪 - 避免嵌套结构(struct/list/map)——PyArrow解嵌套开销大,且无法向量化
多文件并行读取时的线程/进程选择
单个Parquet文件内部是多row group并行可读的,PyArrow默认已启用多线程解码(通过 use_threads=True)。但多个文件一起读时,并行策略要小心。
ThreadPoolExecutor 在IO密集场景够用,但若文件在本地磁盘且数量多,GIL影响不大;若走网络存储(S3/HDFS),建议用 concurrent.futures.ProcessPoolExecutor 避免阻塞主线程。
- 单机多文件 + 本地存储:
ThreadPoolExecutor(max_workers=4)+ 每个任务调read_table(..., use_threads=True) - 单机多文件 + S3路径:
ProcessPoolExecutor(max_workers=2),并在子进程中初始化s3fs.S3FileSystem - 切忌:在主线程用
threading.Thread手动管理,容易因未join或异常退出导致句柄泄漏
最易被忽略的是文件系统缓存——PyArrow不会自动缓存S3对象,如果反复读同一份文件,务必自己加LRU cache包装 read_table,否则每次都是网络往返。