#数据访问 #内存映射 #零拷贝 #无等待 #进程间 #同步 #共享内存

mmap-sync

一个Rust包,允许在映射内存中通过无等待和零拷贝的方式在进程间共享数据

5个稳定版本

1.0.4 2024年8月13日
1.0.3 2024年7月10日
1.0.2 2023年9月29日
1.0.1 2023年6月21日
1.0.0 2023年6月15日

#77操作系统 类别中

Download history 596/week @ 2024-04-28 247/week @ 2024-05-05 466/week @ 2024-05-12 314/week @ 2024-05-19 314/week @ 2024-05-26 259/week @ 2024-06-02 318/week @ 2024-06-09 315/week @ 2024-06-16 341/week @ 2024-06-23 446/week @ 2024-06-30 942/week @ 2024-07-07 710/week @ 2024-07-14 525/week @ 2024-07-21 931/week @ 2024-07-28 1249/week @ 2024-08-04 2174/week @ 2024-08-11

每月4,909次下载

Apache-2.0

42KB
551

mmap-sync

build docs.rs crates.io License

mmap-sync 是一个Rust crate,旨在管理单个写入进程和多个读取进程之间的高性能、并发数据访问,利用内存映射文件、无等待同步和零拷贝反序列化的优势。我们在博客文章中使用了 mmap-sync 用于大规模机器学习: "每个请求,每个微秒:在Cloudflare上的可扩展机器学习"

概述

mmap-sync 的核心是一个 Synchronizer 结构,它提供了一个简单的接口,具有 "写入" 和 "读取" 方法,允许用户读取和写入实现或派生某些 rkyv 特性的任何 Rust struct (T)。

impl Synchronizer {
    /// Write a given `entity` into the next available memory mapped file.
    pub fn write<T>(&mut self, entity: &T, grace_duration: Duration) -> Result<(usize, bool), SynchronizerError> {}

    /// Reads and returns `entity` struct from mapped memory wrapped in `ReadResult`
    pub fn read<T>(&mut self) -> Result<ReadResult<T>, SynchronizerError> {}
}

数据存储在共享映射内存中,允许 Synchronizer 同时从其中 "写入" 和 "读取"。这使得 mmap-sync 成为一个高效且灵活的工具,用于管理共享的并发数据访问。

映射内存

使用内存映射文件比其他进程间通信 (IPC) 机制具有几个优点。它允许不同的进程访问相同的内存空间,绕过昂贵的序列化和反序列化需求。这种设计使 mmap-sync 能够在进程间提供极快、低开销的数据共享。

无等待同步

我们无等待的数据访问模式借鉴了来自 Linux内核的读-复制-更新 (RCU) 模式左右并发控制技术 的灵感。在我们的解决方案中,我们在不同的内存映射文件中维护数据的两份副本。对数据的写入由单个写入者管理,多个读取者能够并发访问数据。

我们将在一个称为“状态”的第三方内存映射文件中存储同步状态,该状态协调对数据副本的访问。该文件包含一个原子64位整数,代表一个InstanceVersion和一对额外的原子32位变量,用于跟踪每个数据副本的活跃读取器数量。InstanceVersion由当前活跃的数据文件索引(1位)、数据大小(39位,可容纳数据大小高达549 GB)和数据校验和(24位)组成。

零拷贝反序列化

为了高效地存储和检索数据,mmap-sync利用rkyv库的帮助,直接引用序列化形式的字节。这显著减少了访问和使用数据所需的时间和内存。为Synchronizer提供的模板类型T可以是任何实现了指定rkyv特质的Rust结构体。

入门指南

要使用mmap-sync,将其添加到您的Cargo.toml中的[dependencies]

[dependencies]
mmap-sync = "1"

然后,在您的Rust程序中导入mmap-sync

use mmap_sync::synchronizer::Synchronizer;

查看提供的示例以获取详细用法

  • 写入进程示例:此示例演示了如何使用mmap-sync定义Rust结构并将其写入共享内存。
  • 读取进程示例:此示例展示了如何读取写入进程写入到共享内存中的数据。

这些示例共享一个公共模块,该模块定义了要写入和读取的数据结构。

要运行这些示例,请按照以下步骤操作

  1. 打开终端并导航到您的项目目录。
  2. 使用以下命令执行写入示例:cargo run --example writer
  3. 同样,使用以下命令运行读取示例:cargo run --example reader

在成功执行这些示例后,终端输出应类似于

# Writer example
written: 36 bytes | reset: false
# Reader example
version: 7 messages: ["Hello", "World", "!"]

此外,还将创建以下文件

$ stat -c '%A %s %n' /tmp/hello_world_*
-rw-r----- 36 /tmp/hello_world_data_0
-rw-r----- 36 /tmp/hello_world_data_1
-rw-rw---- 16 /tmp/hello_world_state

按照这些步骤,您可以在Rust应用程序中开始利用mmap-sync进行高效的进程间并发数据访问。

性能调整

使用tmpfs卷将减少磁盘I/O延迟,因为它直接在RAM上运行,与传统的基于磁盘的存储相比,提供更快的读写能力

let mut synchronizer = Synchronizer::new("/dev/shm/hello_world".as_ref());

此更改将同步器指向位于tmpfs文件系统中的共享内存对象,该文件系统通常在大多数Linux系统中挂载在/dev/shm。这应该有助于缓解与磁盘I/O相关的部分瓶颈。如果/dev/shm没有足够的空间或您想要创建一个专用的tmpfs实例,您可以使用以下命令设置自己的实例。例如,要创建一个1GB的tmpfs卷,您可以使用以下命令

sudo mount -t tmpfs -o size=1G tmpfs /mnt/mytmpfs

基准测试

要运行基准测试,您首先需要安装cargo-criterion二进制文件

cargo install cargo-criterion

然后您将能够使用以下命令运行基准测试

cargo criterion --bench synchronizer

以下基准测试是在搭载第 13 代英特尔Core(R) i7-13800H 处理器的 Linux 笔记本上执行的,编译器标志设置为 RUSTFLAGS=-C target-cpu=native

synchronizer/write
    time:   [250.71 ns 251.42 ns 252.41 ns]
    thrpt:  [3.9619 Melem/s 3.9774 Melem/s 3.9887 Melem/s]

synchronizer/write_raw
    time:   [145.25 ns 145.53 ns 145.92 ns]
    thrpt:  [6.8531 Melem/s 6.8717 Melem/s 6.8849 Melem/s]

synchronizer/read/check_bytes_true
    time:   [40.114 ns 40.139 ns 40.186 ns]
    thrpt:  [24.884 Melem/s 24.914 Melem/s 24.929 Melem/s]

synchronizer/read/check_bytes_false
    time:   [26.658 ns 26.673 ns 26.696 ns]
    thrpt:  [37.458 Melem/s 37.491 Melem/s 37.512 Melem/s]

依赖项

约 2.5MB
约 61K SLoC