从特定位置的二进制文件中读取整数的性能问题

时间:2014-06-24 07:45:40

标签: python numpy binaryfiles

我有一个整数存储为二进制的文件,我试图在特定位置提取值。它是一个大的序列化整数数组,我需要特定索引的值。我创建了以下代码,但与之前创建的F#版本相比,它的速度非常慢。

import os, struct

def read_values(filename, indices):
    # indices are sorted and unique
    values = []
    with open(filename, 'rb') as f:
        for index in indices:
            f.seek(index*4L, os.SEEK_SET)
            b = f.read(4)
            v = struct.unpack("@i", b)[0]
            values.append(v)
    return values

为了比较,这里是F#版本:

open System
open System.IO

let readValue (reader:BinaryReader) cellIndex = 
    // set stream to correct location
    reader.BaseStream.Position <- cellIndex*4L
    match reader.ReadInt32() with
    | Int32.MinValue -> None
    | v -> Some(v)

let readValues fileName indices = 
    use reader = new BinaryReader(File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
    // Use list or array to force creation of values (otherwise reader gets disposed before the values are read)
    let values = List.map (readValue reader) (List.ofSeq indices)
    values

有关如何提高python版本性能的任何提示,例如:使用numpy?

更新

Hdf5工作得非常好(我的测试文件从5秒到0.8秒):

import tables
def read_values_hdf5(filename, indices):
    values = []
    with tables.open_file(filename) as f:
        dset = f.root.raster
        return dset[indices]

更新2

我使用了np.memmap,因为性能类似于hdf5,我已经在生产中使用了numpy。

2 个答案:

答案 0 :(得分:4)

根据您的索引文件大小,您可能希望将其完全读入numpy数组。如果文件不大,则完整的顺序读取可能比大量的搜索更快。

搜索操作的一个问题是python在缓冲输入上运行。如果程序是用某种低级语言编写的,那么在无缓冲IO上使用将是一个好主意,因为你只需要几个值。

import numpy as np

# read the complete index into memory
index_array = np.fromfile("my_index", dtype=np.uint32)
# look up the indices you need (indices being a list of indices)
return index_array[indices]

如果您无论如何都会读取几乎所有页面(即您的索引是随机的并且频率为1/1000或更高),这可能会更快。另一方面,如果你有一个大的索引文件,并且你只想选择一些索引,那就不那么快了。

然后,另一种可能性 - 可能是最快的 - 是使用python mmap模块。然后文件被内存映射,只访问真正需要的页面。

它应该是这样的:

import mmap

with open("my_index", "rb") as f:
    memory_map = mmap.mmap(mmap.mmap(f.fileno(), 0)
    for i in indices:
        # the index at position i:
        idx_value = struct.unpack('I', memory_map[4*i:4*i+4])

(注意,我实际上没有测试过那个,所以可能会有输入错误。另外,我不关心endianess,所以请检查它是否正确。)

令人高兴的是,可以使用numpy.memmap来组合这些。它应该将您的阵列保留在磁盘上,但会给您带来numpyish索引。它应该像以下一样简单:

import numpy as np

index_arr = np.memmap(filename, dtype='uint32', mode='rb')
return index_arr[indices]

我认为这应该是最简单,最快捷的选择。但是,如果&#34;快速&#34;很重要,请测试和分析。


编辑:由于mmap解决方案似乎越来越流行,我将添加一些关于内存映射文件的文字。

什么是mmap?

内存映射文件不是pythonic独有的东西,因为内存映射是POSIX标准中定义的内容。内存映射是一种使用设备或文件的方式,就好像它们只是内存中的区域一样。

文件内存映射是随机访问固定长度数据文件的一种非常有效的方法。它使用与虚拟内存相同的技术。读写是普通的内存操作。如果它们指向不在物理RAM存储器中的存储器位置(&#34;页面错误&#34;发生),则将所需的文件块(页面)读入存储器。

随机文件访问的延迟主要是由于磁盘的物理旋转(SSD是另一个故事)。平均而言,您需要的挡块旋转半圈;对于典型的HDD,此延迟大约为5 ms加上任何数据处理延迟。与此延迟相比,使用python而不是编译语言引入的开销可以忽略不计。

如果按顺序读取文件,操作系统通常会使用预读缓存来缓冲文件,甚至在您知道需要之前。对于随机访问的大文件,这根本没有用。内存映射提供了一种非常有效的方法,因为所有块都在您需要时准确加载并保留在缓存中以供进一步使用。 (这原则上也可以与fseek一起发生,因为它可能在幕后使用相同的技术。但是,无法保证,当呼叫在操作系统中漫游时,无论如何都会有一些开销。)

mmap也可用于编写文件。从某种意义上讲,它非常灵活,可以由多个进程共享单个内存映射文件。在某些情况下,这可能非常有用和高效,mmap也可用于进程间通信。在这种情况下,通常没有为mmap指定文件,而是创建了内存映射,后面没有文件。

mmap尽管有用且易于使用,但并不是很有名。然而,它有一个重要的问题。文件大小必须保持不变。如果它在mmap期间发生变化,可能会发生奇怪的事情。

答案 1 :(得分:1)

索引列表是否排序?我认为如果对列表进行排序,你可以获得更好的性能,因为你可以减少磁盘搜索次数