内存映射 IO 简介

B站影视 2025-01-23 12:58 2

摘要:内存映射文件是虚拟内存中的一个段。虚拟内存是由操作系统(OS) 提供给进程的物理内存的抽象。如果操作系统内存不足或看到进程长时间空闲,则此类虚拟内存段将被“分页”到物理磁盘位置(“交换”)。因此,“swap” 本质上是磁盘上虚拟内存的一部分。

内存映射文件是虚拟内存中的一个段。虚拟内存是由操作系统 (OS) 提供给进程的物理内存的抽象。如果操作系统内存不足或看到进程长时间空闲,则此类虚拟内存段将被“分页”到物理磁盘位置(“交换”)。因此,“swap” 本质上是磁盘上虚拟内存的一部分。

更准确地说,内存映射文件是虚拟内存上完全由操作系统管理的部分(或整个)文件的镜像

换句话说,每当对文件进行内存映射时,操作系统都会立即将文件内存映射到虚拟内存上的区域。您可以将其视为一些 RAM 空间。每当操作系统将文件内容转储到磁盘(以适应其他任务)时,都可能发生页面错误。这时操作系统返回并将内容重新加载回 RAM。但是我们的程序永远不会看到这种变化,而是偶尔会面临内存映射 IO 的延迟。

在本文中,我只讨论 Memory Mapped Writing。再读一次!我将以一个与生物信息学相关的任务作为运行示例。随意将其转换为您自己的域并尝试一下。

使用内存映射文件的原因有很多,也有很多理由不使用内存映射文件。我只谈谈优点!

排比: 内存映射区域可以被认为是一个巨大的字节数组。因此,你可以从尽可能多的线程中写入 / 读取。甚至在进程之间共享。只要不发生争用条件。

性能: 内存映射写入通常很快,因为不使用流/文件缓冲区。OS 执行实际的文件写入,通常一次以几千字节的块为单位。

在此示例中,我们的任务如下;

给定 100 万个 DNA 序列,将它们矢量化并以文本形式编写,以用于未来的机器学习任务。请注意,每个向量都有 136 维的固定大小,并使用函数 vectorize(sequence) 获得,因为它是如何完成的并不相关。为了让它更有趣,让我们逐步改进我们的程序,从单线程、常规文件写入开始。

将简单地读取序列并矢量化。结果向量将转换为字符串并写入输出缓冲区。此处提供了 C++ 代码摘要。

while (seq := has_sequence(input_file)) vector := vectorize(seq) output

在多线程操作中,我们需要确保我们的 output 与 input 的顺序相同。否则,我们可能会忘记哪个数据点是哪个数据点。因此,我将使用数据队列进行读取,并使用使用者进行批处理并并行处理。这两个事件是异步发生的,因此队列将始终处于满状态。

Queue = {}thread_1while (seq := has_sequence(input_file) and Queue.size

请注意,为了清楚起见,我省略了互斥锁和其他细粒度的同步技巧。此处提供了 C++ 代码摘要。我使用条件变量来访问队列和终止线程。

对于内存映射操作,需要记住的事项很少。创建新的内存映射文件时,我们必须指定大小。这是因为我们无法动态地增加 OS 分配给我们的区域(我们可以通过复制来增加大小,就像调整数组大小一样。这可能会非常缓慢)。因此,我们需要了解总输出大小。这对我们来说很容易,因为我们知道向量的大小。我们还可以通过对数据集快速迭代一次来获取数据项的总数。所以我们的代码将如下所示;

num_sequences := iterate_once_get_seqsvector_size := 136float_size := 8 size_of_line := vector_size * (float_size + 1)size_of_file := size_of_line * num_sequencesmmap_file := mmap.open(output, size_of_file, write_mode)

请注意,此时我们已经制作了一个内存映射文件。我们固定浮点值长度(填充零或截断小数精度)。以固定宽度的方式书写时,我们的生活变得轻松。

vectorize_function: lock(mutex) seq := has_sequence(input_file) unlock(mutex) if (seq): vec := vectorize(seq) mmapfile.write(offset, to_string(vec))thread_pool(many threads)for _ in range(num workers) thread_pool.add_worker(vectorize_function, mmapfile)

请注意,我们有一个 offset 参数。这可以通过以下方程式获得;

offset := size_of_line * sequence_id

Sequence id 是 dataset 中从 0 开始的序列索引。我们将内存映射文件指针从起点递增到 offset 值,并在该偏移量处复制字符串向量。操作系统负责其余的工作。

请注意,我们在读取文件时只有一个互斥锁!不再有 blocked 调用或序列批处理。

我使用 /usr/bin/time -v program.o 命令来获取每种方法使用的 CPU 和 Wall 时间。结果如下。

图片由作者提供

请注意,CPU 使用率已从 80% 增加到 420%。它也几乎比批处理多线程方法翻了一番。CPU 时间几乎保持不变,因为我们要做的计算量大致相同。Wall Time 已减少到四分之一。但是,RAM 使用量已从 2MB 增加到近 1GB,因为操作系统将内存映射文件保存在 RAM 中。由于我有足够的 RAM,操作系统似乎一直将文件保持在 RAM 中。使用 linux diff 命令验证结果。

来源:自由坦荡的湖泊AI

相关推荐