
Mizu 发布:基于共享内存实现 R 与 Python 零拷贝并行通信
来源标题:Mizu – zero-copy parallel R and Python over shared memory
Mizu 提供基于共享内存的零拷贝通信层,让 Python 和 R 之间的任务往返从约 100 微秒降至 1 微秒以内。其通道和任务池支持列表、字典、numpy 数组和 Arrow 表等结构跨语言共享视图,并保持进程级故障隔离。项目目前处于实验阶段,API 尚不稳定,可通过 pip install pymizu 安装。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
通过共享内存实现线程级速度的异步与并行 Python。
任务往返时间从通过 socket 的约 100 µs 降至不足 1 µs,因此你可以像使用线程那样对工作进行细粒度划分(参见基准测试)。列表、字典、numpy 数组和 Arrow 表等常见数据结构可以在 Python 和 R 之间以零拷贝视图的形式共享。
在默认的 CPython 构建中,GIL 一次只能在一个核心上运行 CPU 密集型线程,因此 Python 中的计算并行通常意味着多进程。
进程还能隔离故障:某个 worker 崩溃不会连带拖垮宿主进程。
通道和 work-stealing 任务池运行在 POSIX 共享内存(Linux、macOS)或 Win32 文件映射(Windows)之上:无锁的单生产者单消费者环形缓冲区,支持批量发布;等待策略为先自旋后挂起,在热路径上不会进入内核;当对端死亡时会收到操作系统通知。
pymizu 构建于 libmizu 之上,后者是一个用于无锁共享内存 IPC 的 C 库。
mizu 为 R 绑定了同一套核心,因此 Python 和 R 可以互相通信。
生命周期:实验阶段。API 尚未稳定,我们可能会根据反馈做出更改。
pip install pymizu
或从 GitHub 安装开发版本:
pip install git+https://github.com/shikokuchuo/pymizu
需要 Python 3.10 或更高版本,且平台为 64 位。
该扩展会编译随附的 C 核心,因此无需系统库。
若要在 GitHub 安装时请求额外依赖,请使用 pip install "pymizu[numpy] @ git+https://github.com/shikokuchuo/pymizu"。
import pymizu with pymizu.Pool.create(4) as pool: task = pool.submit(pow, 2, 16) print(task.collect(timeout=5))
基准测试
与标准库 concurrent.futures 池的通信开销对比(Apple M4 Pro,来自 benchmarks/mizu-stdlib-bench.py):
对比 ProcessPoolExecutor(任务在独立进程中运行,负载需要 pickle 序列化):
对比 ThreadPoolExecutor(任务共享同一进程,因此 GIL 将 CPU 密集型工作限制在单个核心上):
benchmarks/mizu-bench.py 可单独运行 pymizu 各行。
了解 pymizu 与 Ray、Dask、joblib 以及现代 CPython 的对比。
- elt = 元素;每个 map 元素的墙钟时间,单位为微秒。
PoolExecutor 将池适配为标准库 concurrent.futures.Executor 接口,因此针对 ProcessPoolExecutor 编写的代码可以直接替换使用。
submit 返回真正的 Future 对象,因此 wait / as_completed / asyncio.wrap_future 均可照常使用:
with pymizu.PoolExecutor.create(4) as ex: futures = [ex.submit(pow, 2, i) for i in range(4)] print([f.result() for f in futures])
并行 map
Pool.map(fn, x) 在池上对 x 映射 fn,并立即返回一个 Map 句柄,正如 Pool.submit() 返回一个任务一样。对句柄调用 collect() 会等待并按顺序返回结果值。
函数和数据只进入共享内存一次。
Worker 在彼此之间划分元素,从共享游标中领取区间。
map 还支持嵌套:被映射的函数可以通过 pymizu.current_pool() 在自己的池上进行 map,且不会死锁。
with pymizu.Pool.create(4) as pool: m = pool.map(abs, range(-5, 5)) print(m.collect())
R 互操作
池的 worker 可以是运行 mizu 包的 R 进程,mizu 是同一核心的 R 绑定。
pymizu.r_launcher() 负责启动它们,pymizu.call() 指定任务。下面这个例子在 R 中汇总内置的 mtcars,再读回 polars:
import polars as pl import pymizu with pymizu.Pool.create(4, launcher=pymizu.r_launcher()) as pool: task = pool.submit( pymizu.call(source="aggregate(mpg ~ cyl, data = mtcars, FUN = mean)") ) print(pl.DataFrame(task.collect()))
numpy 数组传入 R 时成为 numeric 向量,polars、pyarrow 或 pandas 数据框传入时成为 data frame
向量或数据框返回时成为 numpy 数组或 pymizu.Frame
Arrow 数据(pyarrow 表与 record batch reader,或任何 arrow_c_stream 生产者)以同样的方式传递
R 互操作页面给出了完整契约:可移植子集、pymizu.Frame、零拷贝数据框以及 dtype 矩阵。
mizu README 展示了反方向:由 R 宿主通过 mizu_py_launcher() 驱动 Python worker。
文档站点覆盖了整个包:通道及其传递的内容、任务池(批量操作、嵌套任务、concurrent.futures 执行器、监视运行中的池)、并行 map(调度、可复现随机性、模板、预备 map)、R 互操作与 dtype 矩阵、Linux 上的部署,以及 API 参考。
仓库结构与贡献者说明参见 CONTRIBUTING.md。
MIT。
LICENSE.note 包含随附 libmizu 核心的 RngStreams 署名信息。
请注意,本项目发布时附带贡献者行为准则。参与本项目即表示你同意遵守其条款。
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。