jjzjj

python - 以 block 的形式处理比物理内存大得多的数据

coder 2023-08-18 原文

我需要处理一些比 RAM 大几百倍的数据。我想读一大块,处理它,保存结果,释放内存并重复。有没有办法在 Python 中提高效率?

最佳答案

总的来说关键是你要迭代处理文件。

如果您只是处理一个文本文件,这很简单:for line in f: 一次只读取一行。 (实际上它缓冲了东西,但缓冲区足够小,你不必担心。)

如果你正在处理一些其他特定的文件类型,比如 numpy 二进制文件、CSV 文件、XML 文档等,通常有类似的专用解决方案,但没有人可以向你描述它们,除非你告诉我们你有什么样的数据。

但是如果你有一个通用的二进制文件呢?


首先,read方法需要一个可选的最大字节数来读取。所以,而不是这个:

data = f.read()
process(data)

你可以这样做:

while True:
    data = f.read(8192)
    if not data:
        break
    process(data)

您可能想编写这样的函数:

def chunks(f):
    while True:
        data = f.read(8192)
        if not data:
            break
        yield data

那么你可以这样做:

for chunk in chunks(f):
    process(chunk)

您也可以使用双参数 iter 来完成此操作,但很多人觉得这有点晦涩:

for chunk in iter(partial(f.read, 8192), b''):
    process(chunk)

无论哪种方式,此选项都适用于下面的所有其他变体(单个 mmap 除外,它非常琐碎,没有意义)。


那里的数字 8192 没有什么神奇之处。您通常需要 2 的幂,最好是系统页面大小的倍数。除此之外,无论您使用的是 4KB 还是 4MB,您的性能都不会发生太大变化 — 如果发生变化,您将必须测试哪种方式最适合您的用例。


无论如何,这假设您可以一次处理每个 8K,而无需保留任何上下文。例如,如果您要将数据输入渐进式解码器或哈希器或其他东西,那就太完美了。

但是如果您需要一次处理一个“ block ”,您的 block 最终可能会跨越 8K 边界。你是如何处理的?

这取决于文件中 block 的分隔方式,但基本思想非常简单。例如,假设您使用 NUL 字节作为分隔符(不太可能,但作为玩具示例很容易展示)。

data = b''
while True:
    buf = f.read(8192)
    if not buf:
        process(data)
        break
    data += buf
    chunks = data.split(b'\0')
    for chunk in chunks[:-1]:
        process(chunk)
    data = chunks[-1]

这种代码在网络中很常见(因为套接字 不能只是“读取所有”,所以你总是必须读入缓冲区并分 block 放入消息中),因此您可能会在使用与您的文件格式类似的协议(protocol)的网络代码中找到一些有用的示例。


或者,您可以使用 mmap .

如果您的虚拟内存大小大于文件,这是微不足道的:

with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ) as m:
    process(m)

现在 m 就像一个巨大的 bytes 对象,就像您调用 read() 将整个对象读入内存一样— 但操作系统会根据需要自动将位分页进出内存。


如果您尝试读取一个太大而无法容纳您的虚拟内存大小的文件(例如,一个 4GB 的 32 位 Python 文件,或一个 20EB 的 64 位 Python 文件——这只可能发生在2013 如果你正在读取一个稀疏或虚拟文件,比如 Linux 上另一个进程的 VM 文件),你必须实现窗口化——一次在一个文件中进行 mmap。例如:

windowsize = 8*1024*1024
size = os.fstat(f.fileno()).st_size
for start in range(0, size, window size):
    with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ, 
                   length=windowsize, offset=start) as m:
        process(m)

当然映射窗口和读取 block 一样的问题,如果你需要分隔东西,你可以用同样的方法解决。

但是,作为一种优化,您可以将窗口向前滑动到包含最后一条完整消息末尾的页面而不是缓冲,而不是一次 8MB,这样您就可以避免任何复制。这有点复杂,所以如果你想这样做,搜索“滑动 mmap 窗口”之类的东西,如果你卡住了,写一个新问题。

关于python - 以 block 的形式处理比物理内存大得多的数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17710748/

有关python - 以 block 的形式处理比物理内存大得多的数据的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  5. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  6. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  7. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  8. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  9. ruby-on-rails - 使用回形针的嵌套形式 - 2

    我有一个名为posts的模型,它有很多附件。附件模型使用回形针。我制作了一个用于创建附件的独立模型,效果很好,这是此处说明的View(https://github.com/thoughtbot/paperclip):@attachment,:html=>{:multipart=>true}do|form|%>posts中的嵌套表单如下所示:prohibitedthispostfrombeingsaved:@attachment,:html=>{:multipart=>true}do|at_form|%>附件记录已创建,但它是空的。文件未上传。同时,帖子已成功创建...有什么想法吗?

  10. ruby - 在匿名 block 中产生 - 2

    我没有理解以下行为(另请参阅inthisSOthread):defdef_testputs'def_test.in'yieldifblock_given?puts'def_test.out'enddef_testdoputs'def_testok'endblock_test=procdo|&block|puts'block_test.in'block.callifblockputs'block_test.out'endblock_test.calldoputs'block_test'endproc_test=procdoputs'proc_test.in'yieldifblock_gi

随机推荐