文件大小会影响Python写入性能吗?

4 投票
3 回答
2085 浏览
提问于 2025-04-19 02:35

我试着用Python往一个文件里写大约50亿行的数据。我发现随着文件越来越大,写入的速度变得越来越慢。

比如一开始我能每秒写1000万行,但在写到30亿行的时候,速度变成之前的十分之一。

我在想,这是不是跟文件的大小有关呢?

也就是说,如果我把这个大文件拆成几个小文件,写入的速度会不会变快,还是说文件的大小对写入速度没有影响。

如果你觉得文件大小会影响速度,能不能解释一下为什么?

-- 额外信息 --

内存使用率一直保持在1.3%。每行的长度也都是一样的。所以我的逻辑是,我从一个文件(我们叫它文件A)里读取一行。文件A的每一行都有两个用制表符分隔的值,如果其中一个值符合某些特定条件,我就把这一行加到文件B里。这个操作是O(1),我只需要把值转换成整数,然后检查这个值对某个数字取余是否符合我想要的7个标志之一。

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

操作系统是Ubuntu。

3 个回答

0

你提到在处理了30亿行代码后,程序崩溃了,而且你的内存使用率一直保持在1.3%。其他人也提到,Python的输入输出代码并不会因为文件大小而影响性能。所以,这可能是软件(操作系统)或硬件的问题!为了帮助解决这个问题,我建议你尝试以下几种方法:

  • 使用 $ time python yourprogram.py 命令来分析你的程序运行时间,这样你会看到以下结果:
  • real - 指的是实际经过的时间 user - 指的是CPU在用户空间花费的时间 sys - 指的是CPU在内核特定功能中花费的时间

    想了解更多关于 realusersys 的信息,可以查看 这个 StackOverflow的回答。

  • 使用逐行计时和执行频率的分析工具,line_profiler 是一个简单且不干扰你代码的工具,可以帮助你查看每行代码在脚本中运行的速度和频率。你可以安装 line_profiler,这是由Robert Kern编写的,你可以通过pip安装这个Python包:
  • $ pip install line_profiler
    

    想了解更多文档可以查看 这里。另外,你也可以安装 memory_profiler 来查看你的代码行使用了多少内存!使用以下命令安装:

    $ pip install -U memory_profiler
    $ pip install psutil
    

    文档可以查看 这里

  • 最后也是最重要的一点是找出内存泄漏在哪里? cPython解释器使用引用计数作为主要的内存管理方法。这意味着每个对象都有一个计数器,当有地方引用这个对象时,计数器加一;当引用被删除时,计数器减一。当计数器变为零时,cPython解释器就知道这个对象不再被使用,于是它会删除这个对象并释放占用的内存。
  • 如果你的程序中仍然持有对不再使用的对象的引用,就可能会发生内存泄漏。

    找出这些“内存泄漏”的最快方法是使用一个叫做objgraph的工具,这是Marius Gedminas编写的。这个工具可以让你查看内存中对象的数量,并找到代码中所有持有这些对象引用的地方。

    使用pip安装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
    

    想了解更多文档可以查看 这里

    来源: 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/

    'real'、'user'和'sys'在time(1)输出中是什么意思?

    2

    导致这个问题的代码并不是Python的一部分。如果你在写入一个对大文件有问题的文件系统,那么你需要检查的是文件系统的驱动程序。

    至于解决办法,可以尝试在你的平台上使用不同的文件系统(不过这样就不再是编程问题了,所以不适合在StackOverflow上讨论)。

    3

    这个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文件系统都使用了一种叫做分配时刷新的磁盘分配方式。Linux系统还会主动尝试移动文件,以确保文件的存储是连续的(而不是分散的)。

    对于Linux的EXT4文件系统,你可以提前分配更大的文件,以减少这种影响。可以使用fallocate命令,具体用法可以参考这个StackOverflow的帖子。然后在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...
    

    撰写回答