从磁盘加载NumPy数组:比较memmap()和Zarr / HDF5

如果您的NumPy数组太大而无法容纳在RAM中,则可以通过将其拆分为片段来对其进行处理您可以以透明模式进行操作,也可以通过一次从磁盘加载这些片段来显式地执行此操作。 在这种情况下,您可以使用两类工具:





  • NumPy方法memmap(),一种透明的机制,使您可以感知磁盘上的文件,就好像它全部在内存中一样。 
  • Zarr和HDF5数据存储格式彼此相似,允许在必要时从磁盘加载并将阵列的压缩片段保存到磁盘。

这些方法中的每一种都有其优点和缺点。 

该材料(我们今天出版的翻译)致力于分析这些数据处理方法的功能,以及在什么情况下可以派上用场的故事。特别是,将特别注意为执行计算而优化的数据格式,而不必将其设计为将数据传输给其他程序员。

从磁盘读取数据或将数据写入磁盘会发生什么?


首次从磁盘读取文件时,操作系统不仅会将数据复制到过程内存。首先,它将这些数据复制到其内存中,并将其副本存储在所谓的“缓冲区缓存”中。

这里有什么用?

事实是,如果您需要再次从同一文件读取同一数据,则操作系统会将数据存储在缓存中。


如果再次读取数据,则它不是从磁盘而是从RAM进入程序存储器,这要快几个数量级。


如果高速缓存占用的内存用于其他用途,则高速缓存将被自动清除。

当数据写入磁盘时,它会以相反的方向移动。首先,它们仅写入缓冲区高速缓存。这意味着写操作通常非常快,因为程序不需要将重点放在慢速磁盘上。在录制期间,她只需要使用RAM。


结果,数据从高速缓存刷新到磁盘。


使用memmap处理数组()


在我们的情况下,memmap()它使我们可以感知磁盘上的文件,就好像它是存储在内存中的数组一样。对程序透明的操作系统根据是否将请求的数据缓存在内存中来执行读/写操作,访问缓冲区缓存或硬盘。这样的算法在这里执行:

  • 数据在缓存中吗?如果是的话-非常好-您可以直接与他们联系。
  • 数据在磁盘上吗?访问它们的速度较慢,但​​是您不必担心,它们将以透明模式加载。

另外memmap()要注意的是,在大多数情况下,文件的缓冲区高速缓存将内置到程序存储器中。这意味着系统不必在缓冲区外的程序存储器中维护数据的其他副本。


该方法memmap()内置在NumPy中:

import numpy as np
array = np.memmap("mydata/myarray.arr", mode="r",
                  dtype=np.int16, shape=(1024, 1024))

运行此代码,您将拥有一个数组供您使用,该数组的工作对于程序是完全透明的-不管是使用缓冲区高速缓存还是使用硬盘进行工作。

Memmap()的局限性


尽管在某些情况下它memmap()可以很好地显示自己,但是此方法也有局限性:

  • 数据必须存储在文件系统中。无法从AWS S3等二进制存储下载数据。
  • , . , . , , , .
  • N- , , , . .

让我们解释最后一点。想象一下,我们有一个包含32位(4字节)整数的二维数组。每个磁盘读取4096个字节。如果您从磁盘顺序读取位于文件中的数据(例如,这些数据在数组的行中),则在每次读取操作之后,我们将有1024个整数。但是,如果您读取的数据在文件中的位置与数组中的位置不匹配(例如,数据位于列中),则每次读取操作将只允许您获得1个必需的编号。结果,事实证明,要获得相同数量的数据,您必须执行一千次以上的读取操作。

Zarr和HDF5


为了克服上述限制,您可以使用Zarr或HDF5 数据存储格式,它们非常相似:

  • 您可以使用pytablesh5py在Python中使用HDF5文件这种格式比Zarr还要旧,并且有更多限制,但是它的优点是可以在以不同语言编写的程序中使用。
  • Zarr是使用同名的Python包实现的格式。它比HDF5更现代,更灵活,但您只能(至少现在)在Python环境中使用它。根据我的感觉,在大多数情况下,如果不需要对HDF5的多语言支持,值得选择Zarr。例如,Zarr具有更好的多线程支持。

此外,我们将仅讨论Zarr,但是如果您对HDF5格式及其与Zarr的更深层次的比较感兴趣,则可以观看视频。

使用Zarr


Zarr允许您以数组的形式存储数据并将其加载到内存中,并且-以数组的形式写入这些数据。

这是使用Zarr加载数组的方法:

>>> import zarr, numpy as np
>>> z = zarr.open('example.zarr', mode='a',
...               shape=(1024, 1024),
...               chunks=(512,512), dtype=np.int16)
>>> type(z)
<class 'zarr.core.Array'>
>>> type(z[100:200])
<class 'numpy.ndarray'>

请注意,在收到物体的一部分之前,我们将不予处置numpy.ndarray实体zarr.core.array就是元数据。仅从磁盘中加载切片中包含的数据。

我为什么选择Zarr?


  • Zarr规避memmap()了上面讨论的限制
  • 数据片段可以存储在磁盘上,AWS S3存储中或某些可以使用键/值格式记录的存储系统中。
  • 数据片段的大小和结构由程序员确定。例如,可以以能够有效地读取位于多维阵列的不同轴上的信息的方式来组织数据。HDF5确实如此。
  • 片段可以压缩。HDF5也可以这样说。

让我们更详细地讨论最后两点。

片段尺寸


假设我们正在处理大小为30,000 x 3,000的数组。如果您需要读取此数组并沿其轴X移动,并沿其轴移动Y,则可以保存包含该数组数据的片段,如下所示(实际上,很可能需要9个以上的片段):


现在,可以有效地加载X和轴上的数据Y根据程序所需的数据类型,您可以下载例如片段(1、0),(1、1,),(1、2)或片段(0、1),(1、1), (2,1)。

资料压缩


每个片段都可以压缩。这意味着数据可以比磁盘允许您读取未压缩信息的速度更快地进入程序。如果将数据压缩3次,则意味着从磁盘上下载数据的速度是未压缩数据的3倍,减去处理器解压缩数据所花费的时间。


下载片段后,可以将其从程序存储器中删除。

摘要:memmap()或Zarr?


memmap()使用哪种 更好- 还是Zarr?

Memmap()在这种情况下看起来很有趣:

  • 有许多进程读取同一文件的各个部分。由于应用程序的缘故,这些进程memmap()将能够共享相同的缓冲区缓存。这意味着无论正在运行多少个进程,都只需将数据的一个副本保留在内存中。
  • 开发人员不希望手动管理内存。他计划仅依靠操作系统的功能,该功能将自动,无形地解决开发人员的所有内存管理问题。

Zarr在以下情况下特别有用(在某些情况下,如将要指出的,HDF5格式也适用):

  • 数据是从远程源而不是从本地文件系统下载的。
  • 系统的瓶颈很可能将从磁盘读取。数据压缩将允许更有效地利用硬件功能。这也适用于HDF5。
  • 如果需要获取沿不同轴的多维数组的切片,Zarr通过选择适当的片段大小和结构来帮助优化此类操作。HDF5确实如此。

我会在memmap()Zarr 之间进行选择,并且首先尝试使用Zarr-因为此包提供了灵活性以及它实现的数据存储格式。

亲爱的读者们!您如何解决使用大型NumPy阵列的问题?


All Articles