【问题标题】:Reading multiple Python pickled data at once, buffering and newlines?一次读取多个 Python 腌制数据,缓冲和换行?
【发布时间】:2011-07-27 07:11:34
【问题描述】:

给你上下文:

我有一个大文件f,有几个 Gigs 大小。它包含通过运行生成的不同对象的连续泡菜

for obj in objs: cPickle.dump(obj, f)

我想在读取这个文件时利用缓冲。我想要的是一次将 几个 拾取的对象读入缓冲区。这样做的最佳方法是什么?我想要一个类似于readlines(buffsize) 的腌制数据。事实上,如果选择的数据确实是换行符分隔的,可以使用 readlines,但我不确定这是否属实。

我想到的另一个选项是先将dumps() 腌制对象写入字符串,然后将字符串写入文件,每个字符串由换行符分隔。要读回文件,我可以使用readlines()loads()。但我担心腌制的对象可能有"\n" 字符,它会抛出这个文件读取方案。我的恐惧是没有根据的吗?

一种选择是将其腌制为一个庞大的对象列表,但这需要的内存超出了我的承受能力。设置可以通过多线程加速,但我不想在缓冲正常工作之前去那里。这种情况的“最佳做法”是什么。

编辑: 我还可以将原始字节读入缓冲区并在其上调用加载,但我需要知道加载消耗了该缓冲区的多少字节,以便我可以扔掉头。

【问题讨论】:

  • 应该已经在后台进行缓冲了。线程也无济于事。
  • 如果您可以控制 pickle 文件的创建,您可以使用pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)(或等效的pickle.dump(obj, f, -1))使其更小,这是一个二进制协议,并且更紧凑比你得到的默认 ASCII 码。拥有一个小得多的文件可能会减轻对缓冲的担忧。事实上,这可能意味着@Kirk Strauser 的answer 中的“寻找以'.\n' 结尾的行”的技巧不起作用。

标签: python newline pickle buffering


【解决方案1】:

我认为你不需要做任何事情。

>>> import pickle
>>> import StringIO
>>> s = StringIO.StringIO(pickle.dumps('apples') + pickle.dumps('bananas'))
>>> pickle.load(s)
'apples'
>>> pickle.load(s)
'bananas'
>>> pickle.load(s)

Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    pickle.load(s)
  File "C:\Python26\lib\pickle.py", line 1370, in load
    return Unpickler(file).load()
  File "C:\Python26\lib\pickle.py", line 858, in load
    dispatch[key](self)
  File "C:\Python26\lib\pickle.py", line 880, in load_eof
    raise EOFError
EOFError
>>> 

【讨论】:

    【解决方案2】:

    file.readlines() 返回文件全部内容的列表。你会想一次读几行。我认为这个天真的代码应该解开你的数据:

    import pickle
    infile = open('/tmp/pickle', 'rb')
    buf = []
    while True:
        line = infile.readline()
        if not line:
            break
        buf.append(line)
        if line.endswith('.\n'):
            print 'Decoding', buf
            print pickle.loads(''.join(buf))
            buf = []
    

    如果您对生成泡菜的程序有任何控制权,我会选择以下之一:

    1. 使用shelve 模块。
    2. 在将每个 pickle 写入文件之前打印其长度(以字节为单位),这样您就可以准确地知道每次要读取多少字节。
    3. 与上述相同,但将整数列表写入单独的文件,以便您可以将这些值用作保存泡菜的文件的索引。
    4. 一次腌制 K 个对象的列表。以字节为单位写入该泡菜的长度。写泡菜。重复。

    顺便说一句,我怀疑file 的内置缓冲应该可以为您带来 99% 的性能提升。

    如果您确信 I/O 阻止了您,您是否考虑过尝试 mmap() 并让操作系统一次处理打包?

    #!/usr/bin/env python
    
    import mmap
    import cPickle
    
    fname = '/tmp/pickle'
    infile = open(fname, 'rb')
    m = mmap.mmap(infile.fileno(), 0, access=mmap.ACCESS_READ)
    start = 0
    while True:
        end = m.find('.\n', start + 1) + 2
        if end == 1:
            break
        print cPickle.loads(m[start:end])
        start = end
    

    【讨论】:

    • 这与我的解决方案非常相似,除了我考虑使用 readllines 对要读入的字节数有预先指定的限制。我是否保证泡菜里面永远不会有换行符。这是我想知道的关键点,否则我们的解决方案是错误的。我没看错,个别泡菜以“.\n”结尾,换句话说,一个点后跟一个换行符?感谢其他建议。现在我一直在缓冲文件对象上调用 cPickle.load() ,并且可以读取大约 200 个对象/秒,每个对象平均大约 800 个字节,这仍然很慢。
    • 我不是专家,但是查看一个包含几千个泡菜的数据库表,它们都以'.\n'结尾。看pickle.dumps('\n');它似乎逃脱了'\ n',尽管我不知道这是否得到保证。您如何看待#4,您在列表中腌制许多项目,然后从单个 file.read() 中解压缩它们?
    • 是的 #4 很好。唯一的缺点是消费者将被限制在固定的“缓冲区”中。但是如果你所说的“.\n”是正确的,那么问题已经解决了:)
    【解决方案3】:

    如果您想为任何文件添加缓冲,请通过io.open() 打开它。这是一个示例,它将以 128K 块从底层流中读取。对cPickle.load() 的每次调用都将从内部缓冲区执行,直到耗尽,然后将从底层文件中读取另一个块:

    import cPickle
    import io
    
    buf = io.open('objects.pkl', 'rb', buffering=(128 * 1024))
    obj = cPickle.load(buf)
    

    【讨论】:

    • 这正是我所做的,事实上,我使用缓冲接口读取了一个 gzip 压缩(压缩级别 2)文件,但我希望能有效地抓取尽可能多的腌制对象。所以我得到的反馈是,如果没有线程,这主要是它的速度。
    【解决方案4】:

    您可能想查看shelve 模块。它使用诸如dbm 之类的数据库模块来创建磁盘上的对象字典。对象本身仍然使用 pickle 进行序列化。这样您就可以一次读取一组对象而不是一个大泡菜。

    【讨论】:

    • 感谢您的回复。不过,我看不出这将如何帮助提高缓冲效率。我正在寻找的是一种读取大量字节并一次解开一些 K 对象的方法。这个想法是为了缓解 i/o 瓶颈。我相信您的建议是我尝试通过提供许多键来加载许多对象。然后该模块查找与这些键对应的值。考虑到我不关心对象的顺序,我只想遍历所有对象,这不是很多额外的工作,因此是倒退了一步。但我可能误会你了。
    猜你喜欢
    • 2018-08-18
    • 1970-01-01
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多