【问题标题】:Does size of a file affects the performance of the write in python文件的大小是否会影响python中写入的性能
【发布时间】:2014-10-25 00:46:55
【问题描述】:

我试图使用 python 将大约 50 亿行写入文件。我注意到随着文件越来越大,写入的性能越来越差。

例如,一开始我每秒写 1000 万行,在 30 亿行之后,它的写速度比以前慢了 10 倍。

我想知道这是否真的与文件的大小有关?

也就是说,如果我将这个大文件分成较小的文件,或者文件的大小不会影响写入的性能,您是否认为性能会变得更好。

如果您认为这会影响性能,请解释原因吗?

--更多信息--

内存消耗始终相同 (1.3%)。线的长度是一样的。所以逻辑是我从文件中读取一行(我们称之为文件A)。文件 A 的每一行包含 2 个制表符分隔值,如果其中一个值具有某些特定特征,我将同一行添加到文件 B。此操作是 O(1),我只是将值转换为 int 并检查该值是否% someNumber 是我想要的 7 个标志中的任何一个。

每次我从文件 A 中读取 10M 行时,我都会输出行号。 (这就是我知道性能下降的方式)。文件 B 越来越大,写入速度越来越慢。

操作系统是 Ubuntu。

【问题讨论】:

  • 您是如何以及何时测量写入速率的?对磁盘的写入在内存中缓冲,因此在连续的写入流期间,您可能会注意到写入实际提交到磁盘时周期性的减速。
  • 操作系统获取文件行的​​速度与 Python 无关; Python I/O 代码中没有任何内容会影响基于文件大小的性能。
  • 您的生产线能否扩大规模?例如,如果较新的行大 10 倍,那么(假设相同的 MB/秒速率)写入的行数应该下降。
  • 您确定这不是您的计算机内存已满吗?程序的其余部分是什么样的?什么操作系统?
  • 内存消耗始终相同(1.3%)。线的长度是一样的。所以逻辑是我从文件中读取一行(我们称之为文件A)。文件 A 的每一行包含 2 个制表符分隔值,如果其中一个值具有某些特定特征,我将同一行添加到文件 B。每次我从文件 A 中读取 10M 行时,我都会输出行号。 (这就是我知道性能下降的方式)。文件 B 是一个越来越大的文件,写入它的速度越来越慢。操作系统是 Ubuntu。谢谢!

标签: python performance file file-io


【解决方案1】:

使用这个 Python 脚本:

from __future__ import print_function
import time
import sys
import platform

if sys.version_info[0]==2:
    range=xrange

times=[]
results=[]
t1=time.time()
t0=t1
tgt=5000000000
bucket=tgt/10
width=len('{:,}  '.format(tgt))
with open('/tmp/disk_test.txt', 'w') as fout:
    for line in range(1,tgt+1):
        fout.write('Line {:{w},}\n'.format(line, w=width))
        if line%bucket==0:
            s='{:15,}   {:10.4f} secs'.format(line, time.time()-t1)
            results.append(s)
            print(s)
            t1=time.time()
    else:
        info=[platform.system(), platform.release(),sys.version, tgt, time.time()-t0]
        s='\n\nDone!\n{} {}\n{} \n\n{:,} lines written in {:10.3f} secs'.format(*info)
        fout.write('{}\n{}'.format(s, '\n'.join(results)))    

print(s)   

在 Python 2 和 OS X 下,打印:

    500,000,000     475.9865 secs
  1,000,000,000     484.6921 secs
  1,500,000,000     463.2881 secs
  2,000,000,000     460.7206 secs
  2,500,000,000     456.8965 secs
  3,000,000,000     455.3824 secs
  3,500,000,000     453.9447 secs
  4,000,000,000     454.0475 secs
  4,500,000,000     454.1346 secs
  5,000,000,000     454.9854 secs

Done!
Darwin 13.3.0
2.7.8 (default, Jul  2 2014, 10:14:46) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] 

5,000,000,000 lines written in   4614.091 secs

在 Python 3.4 和 OS X 下:

    500,000,000     632.9973 secs
  1,000,000,000     633.0552 secs
  1,500,000,000     682.8792 secs
  2,000,000,000     743.6858 secs
  2,500,000,000     654.4257 secs
  3,000,000,000     653.4609 secs
  3,500,000,000     654.4969 secs
  4,000,000,000     652.9719 secs
  4,500,000,000     657.9033 secs
  5,000,000,000     667.0891 secs

Done!
Darwin 13.3.0
3.4.1 (default, May 19 2014, 13:10:29) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] 

5,000,000,000 lines written in   6632.965 secs

生成的文件为 139 GB。您可以看到在一个相对空的磁盘上(我的/tmp 路径是一个 3 TB 的卷)时间是线性的。

我的怀疑是,在 Ubuntu 下,您正在运行操作系统,试图使不断增长的文件在 EXT4 磁盘上保持连续。

回想一下,OS X 的 HFS+ 和 Linux 的 EXT4 文件系统都使用allocate-on-flush 磁盘分配方案。 Linux 操作系统还将尝试主动移动文件以允许分配是连续的(而不是碎片化的)

对于 Linux EXT4——您可以预先分配更大的文件以减少这种影响。使用fallocate,如this SO 帖子中所示。然后在 Python 中倒回文件指针并原地覆盖。

您或许可以使用 Python truncate 方法创建文件,但结果取决于平台。

类似于(伪代码)的东西:

 def preallocate_file(path, size):
     ''' Preallocate of file at "path" of "size" '''
     # use truncate or fallocate on Linux
     # Depending on your platform, You *may* be able to just the following
     # works on BSD and OS X -- probably most *nix:
     with open(path, 'w') as f:
        f.truncate(size)


 preallocate_file(fn, size)
 with open(fn, 'r+') as f:
     f.seek(0)        # start at the beginning 
     # write whatever
     f.truncate()     # erases the unused portion...

【讨论】:

  • 如果你真的想变得花哨——编写你自己的上下文管理器,它的作用类似于with open(...) as f: 表单,但需要一个额外的参数来预分配大小。然后在从上下文管理器退出时进行截断。摇滚!
【解决方案2】:

可能导致这种情况的代码不是 Python 的一部分。如果您要写入的文件系统类型存在大文件问题,则需要检查的代码是文件系统驱动程序。

对于变通方法,为您的平台尝试不同的文件系统(但这不再是编程问题,因此不属于 StackOverflow)。

【讨论】:

  • 如果您的问题提到了您在哪个操作系统上托管 Python 代码,以及您正在写入的文件系统的类型,答案可能会更具体。
  • 有道理,非常感谢!
【解决方案3】:

正如您所说,在 30 亿行之后,您的性能会崩溃,而您的记忆力始终相同(1.3%)!正如其他人提到的那样,Python I/O 代码中没有任何内容会影响基于文件大小的性能。所以它可能是由于软件问题(OS)或硬件问题而发生的!为了解决这个问题,我建议以下方法:

  • 使用$ time python yourprogram.py 命令分析您的时间,显示以下结果:

    real - refers to the actual elasped time user - refers to the amount of cpu time spent outside of kernel sys - refers to the amount of cpu time spent inside kernel specific functions

    阅读更多关于real,user,sys in THIS stachoverflow 回答 ConcernedOfTunbridgeWells。

  • 通过分析器使用逐行计时和执行频率,因此 line_profiler 是一种简单且不显眼的方法来分析您的代码并用于查看每行代码在脚本中运行的速度和频率。 您可以安装由 Robert Kern 编写的line_profiler,您可以通过 pip 安装 python 包:
    $ pip install line_profiler
    

    阅读文档HERE。您也可以安装 memory_profiler 来查找您的线路使用了多少内存!使用此命令安装:

    $ pip install -U memory_profiler
    $ pip install psutil
    

    和文档HERE

  • 最后一个更重要的方法是找到内存泄漏在哪里? cPython 解释器使用引用计数作为跟踪内存的主要方法。这意味着每个对象都包含一个计数器,当对该对象的引用存储在某处时该计数器递增,并在删除对该对象的引用时递减。当计数器达到零时,cPython 解释器知道该对象不再使用,因此它删除该对象并释放占用的内存。

    如果在对象不再使用时仍持有对对象的引用,则程序中经常会发生内存泄漏。

    找到这些“内存泄漏”的最快方法是使用由 Marius Gedminas 编写的名为 objgraph 的出色工具。此工具可让您查看内存中的对象数量,并定位代码中保存对这些对象的引用的所有不同位置。

    使用管道安装 objgraph:

    pip install objgraph
    

    安装此工具后,在代码中插入一条语句来调用调试器:

    import pdb; pdb.set_trace()
    

    哪些对象最常见?

    在运行时,您可以通过运行以下命令检查程序中最流行的 20 个对象: 结果是这样的:

    (pdb) import objgraph
    (pdb) objgraph.show_most_common_types()
    
    MyBigFatObject             20000
    tuple                      16938
    function                   4310
    dict                       2790
    wrapper_descriptor         1181
    builtin_function_or_method 934
    weakref                    764
    list                       634
    method_descriptor          507
    getset_descriptor          451
    type                       439
    

    请阅读文档HERE

    来源: http://mg.pov.lt/objgraph/#python-object-graphs

    https://pypi.python.org/pypi/objgraph

    http://www.appneta.com/blog/line-profiler-python/

    https://sublime.wbond.net/packages/LineProfiler

    http://www.huyng.com/posts/python-performance-analysis/

    What do 'real', 'user' and 'sys' mean in the output of time(1)?

  • 【讨论】:

      猜你喜欢
      • 2011-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-30
      • 1970-01-01
      相关资源
      最近更新 更多