如何在 Python 中廉价地获取大文件的行数

2024-11-29 08:41:00
admin
原创
145
摘要:问题描述:如何以最节省内存和时间的方式获取大文件的行数?def file_len(filename): with open(filename) as f: for i, _ in enumerate(f): pass return i + 1 解决方案 ...

问题描述:

如何以最节省内存和时间的方式获取大文件的行数?

def file_len(filename):
    with open(filename) as f:
        for i, _ in enumerate(f):
            pass
    return i + 1

解决方案 1:

一行,比forOP 的循环更快(尽管不是最快的)并且非常简洁:

num_lines = sum(1 for _ in open('myfile.txt'))

您还可以使用模式并将其包含在关闭文件的块中,从而提高速度(和稳健性):rbU`with`

with open("myfile.txt", "rbU") as f:
    num_lines = sum(1 for _ in f)

注意:自 Python 3.3 及更高版本以来,Uin模式已被弃用,因此我们应该使用 i来代替(并且它已在Python 3.11中删除)。rbU`rb`rbU

解决方案 2:

你不可能得到比这更好的了。

毕竟,任何解决方案都必须读取整个文件,找出`
`有多少个文件,并返回结果。

您有没有更好的方法,无需读取整个文件?不确定...最好的解决方案始终是 I/O 绑定的,您能做的最好的事情就是确保您不使用不必要的内存,但看起来您已经解决了这个问题。

[2023 年 5 月编辑]

正如许多其他答案中所评论的那样,在 Python 3 中有更好的选择。循环for不是最有效的。例如,使用mmap或缓冲区更有效。

解决方案 3:

我相信内存映射文件将是最快的解决方案。我尝试了四个函数:OP 发布的函数(opcount);对文件中的行进行简单迭代(simplecount);使用内存映射文件 (mmap) 的 readline(mapcount);以及 Mykola Kharechko 提供的缓冲区读取解决方案(bufcount)。

我运行了每个函数五次,并计算了 120 万行文本文件的平均运行时间。

Windows XP、Python 2.5、2 GB RAM、2 GHz AMD 处理器

以下是我的结果:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

Python 2.6 的数字:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

因此,缓冲区读取策略似乎是 Windows/Python 2.6 中最快的

以下是代码:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    with open(filename, "r+") as f:
        buf = mmap.mmap(f.fileno(), 0)
        lines = 0
        readline = buf.readline
        while readline():
            lines += 1
        return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('
')
        buf = read_f(buf_size)

    return lines

def opcount(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))

解决方案 4:

所有这些解决方案都忽略了一种可以大大加快运行速度的方法,即使用无缓冲(原始)接口、使用字节数组并进行自己的缓冲。(这仅适用于 Python 3。在 Python 2 中,默认情况下可能会使用或不会使用原始接口,但在 Python 3 中,您将默认使用 Unicode。)

使用修改版本的计时工具,我相信以下代码比提供的任何解决方案都更快(并且稍微更具 Pythonic):

def rawcount(filename):
    f = open(filename, 'rb')
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.raw.read

    buf = read_f(buf_size)
    while buf:
        lines += buf.count(b'
')
        buf = read_f(buf_size)

    return lines

使用单独的生成器函数,运行速度会稍微快一点:

def _make_gen(reader):
    b = reader(1024 * 1024)
    while b:
        yield b
        b = reader(1024*1024)

def rawgencount(filename):
    f = open(filename, 'rb')
    f_gen = _make_gen(f.raw.read)
    return sum(buf.count(b'
') for buf in f_gen)

这可以完全通过使用 itertools 的内联生成器表达式来完成,但它看起来非常奇怪:

from itertools import (takewhile, repeat)

def rawincount(filename):
    f = open(filename, 'rb')
    bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
    return sum(buf.count(b'
') for buf in bufgen)

以下是我的时间安排:

function      average, s  min, s   ratio
rawincount        0.0043  0.0041   1.00
rawgencount       0.0044  0.0042   1.01
rawcount          0.0048  0.0045   1.09
bufcount          0.008   0.0068   1.64
wccount           0.01    0.0097   2.35
itercount         0.014   0.014    3.41
opcount           0.02    0.02     4.83
kylecount         0.021   0.021    5.05
simplecount       0.022   0.022    5.25
mapcount          0.037   0.031    7.46

解决方案 5:

您可以执行子进程并运行wc -l filename

import subprocess

def file_len(fname):
    p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE, 
                                              stderr=subprocess.PIPE)
    result, err = p.communicate()
    if p.returncode != 0:
        raise IOError(err)
    return int(result.strip().split()[0])

解决方案 6:

经过perfplot分析后,必须推荐缓冲读取解决方案

def buf_count_newlines_gen(fname):
    def _make_gen(reader):
        while True:
            b = reader(2 ** 16)
            if not b: break
            yield b

    with open(fname, "rb") as f:
        count = sum(buf.count(b"
") for buf in _make_gen(f.raw.read))
    return count

它速度快,内存利用率高。大多数其他解决方案的速度比它慢 20 倍左右。

在此处输入图片描述


重现情节的代码:

import mmap
import subprocess
from functools import partial

import perfplot


def setup(n):
    fname = "t.txt"
    with open(fname, "w") as f:
        for i in range(n):
            f.write(str(i) + "
")
    return fname


def for_enumerate(fname):
    i = 0
    with open(fname) as f:
        for i, _ in enumerate(f):
            pass
    return i + 1


def sum1(fname):
    return sum(1 for _ in open(fname))


def mmap_count(fname):
    with open(fname, "r+") as f:
        buf = mmap.mmap(f.fileno(), 0)

    lines = 0
    while buf.readline():
        lines += 1
    return lines


def for_open(fname):
    lines = 0
    for _ in open(fname):
        lines += 1
    return lines


def buf_count_newlines(fname):
    lines = 0
    buf_size = 2 ** 16
    with open(fname) as f:
        buf = f.read(buf_size)
        while buf:
            lines += buf.count("
")
            buf = f.read(buf_size)
    return lines


def buf_count_newlines_gen(fname):
    def _make_gen(reader):
        b = reader(2 ** 16)
        while b:
            yield b
            b = reader(2 ** 16)

    with open(fname, "rb") as f:
        count = sum(buf.count(b"
") for buf in _make_gen(f.raw.read))
    return count


def wc_l(fname):
    return int(subprocess.check_output(["wc", "-l", fname]).split()[0])


def sum_partial(fname):
    with open(fname) as f:
        count = sum(x.count("
") for x in iter(partial(f.read, 2 ** 16), ""))
    return count


def read_count(fname):
    return open(fname).read().count("
")


b = perfplot.bench(
    setup=setup,
    kernels=[
        for_enumerate,
        sum1,
        mmap_count,
        for_open,
        wc_l,
        buf_count_newlines,
        buf_count_newlines_gen,
        sum_partial,
        read_count,
    ],
    n_range=[2 ** k for k in range(27)],
    xlabel="num lines",
)
b.save("out.png")
b.show()

解决方案 7:

与此答案类似的单行 Bash 解决方案,使用现代subprocess.check_output函数:

def line_count(filename):
    return int(subprocess.check_output(['wc', '-l', filename]).split()[0])

解决方案 8:

这是一个使用多处理库在机器/核心之间分配行计数的 Python 程序。我的测试使用 8 核 Windows 64 位服务器将计数 2000 万行文件的时间从 26 秒缩短到 7 秒。注意:不使用内存映射会使速度慢得多。

import multiprocessing, sys, time, os, mmap
import logging, logging.handlers

def init_logger(pid):
    console_format = 'P{0} %(levelname)s %(message)s'.format(pid)
    logger = logging.getLogger()  # New logger at root level
    logger.setLevel(logging.INFO)
    logger.handlers.append(logging.StreamHandler())
    logger.handlers[0].setFormatter(logging.Formatter(console_format, '%d/%m/%y %H:%M:%S'))

def getFileLineCount(queues, pid, processes, file1):
    init_logger(pid)
    logging.info('start')

    physical_file = open(file1, "r")
    #  mmap.mmap(fileno, length[, tagname[, access[, offset]]]

    m1 = mmap.mmap(physical_file.fileno(), 0, access=mmap.ACCESS_READ)

    # Work out file size to divide up line counting

    fSize = os.stat(file1).st_size
    chunk = (fSize / processes) + 1

    lines = 0

    # Get where I start and stop
    _seedStart = chunk * (pid)
    _seekEnd = chunk * (pid+1)
    seekStart = int(_seedStart)
    seekEnd = int(_seekEnd)

    if seekEnd < int(_seekEnd + 1):
        seekEnd += 1

    if _seedStart < int(seekStart + 1):
        seekStart += 1

    if seekEnd > fSize:
        seekEnd = fSize

    # Find where to start
    if pid > 0:
        m1.seek(seekStart)
        # Read next line
        l1 = m1.readline()  # Need to use readline with memory mapped files
        seekStart = m1.tell()

    # Tell previous rank my seek start to make their seek end

    if pid > 0:
        queues[pid-1].put(seekStart)
    if pid < processes-1:
        seekEnd = queues[pid].get()

    m1.seek(seekStart)
    l1 = m1.readline()

    while len(l1) > 0:
        lines += 1
        l1 = m1.readline()
        if m1.tell() > seekEnd or len(l1) == 0:
            break

    logging.info('done')
    # Add up the results
    if pid == 0:
        for p in range(1, processes):
            lines += queues[0].get()
        queues[0].put(lines) # The total lines counted
    else:
        queues[0].put(lines)

    m1.close()
    physical_file.close()

if __name__ == '__main__':
    init_logger('main')
    if len(sys.argv) > 1:
        file_name = sys.argv[1]
    else:
        logging.fatal('parameters required: file-name [processes]')
        exit()

    t = time.time()
    processes = multiprocessing.cpu_count()
    if len(sys.argv) > 2:
        processes = int(sys.argv[2])
    queues = [] # A queue for each process
    for pid in range(processes):
        queues.append(multiprocessing.Queue())
    jobs = []
    prev_pipe = 0
    for pid in range(processes):
        p = multiprocessing.Process(target = getFileLineCount, args=(queues, pid, processes, file_name,))
        p.start()
        jobs.append(p)

    jobs[0].join() # Wait for counting to finish
    lines = queues[0].get()

    logging.info('finished {} Lines:{}'.format( time.time() - t, lines))

解决方案 9:

我将使用 Python 的文件对象方法readlines,如下所示:

with open(input_file) as foo:
    lines = len(foo.readlines())

这将打开文件,在文件中创建行列表,计算列表的长度,将其保存到变量并再次关闭文件。

解决方案 10:

这是我使用纯 Python 发现的最快的东西。

您可以通过设置来使用任意数量的内存buffer,但 2**16 似乎是我的计算机上的最佳点。

from functools import partial

buffer=2**16
with open(myfile) as f:
        print sum(x.count('
') for x in iter(partial(f.read,buffer), ''))

我在这里找到了答案:为什么在 C++ 中从 stdin 读取行比在 Python 中慢得多?并对其进行了微小的调整。这是一篇非常好的文章,有助于了解如何快速计算行数,尽管wc -l它仍然比其他任何方法快 75% 左右。

解决方案 11:

def file_len(full_path):
  """ Count number of lines in a file."""
  f = open(full_path)
  nr_of_lines = sum(1 for line in f)
  f.close()
  return nr_of_lines

解决方案 12:

这是我使用的,它看起来很干净:

import subprocess

def count_file_lines(file_path):
    """
    Counts the number of lines in a file using wc utility.
    :param file_path: path to file
    :return: int, no of lines
    """
    num = subprocess.check_output(['wc', '-l', file_path])
    num = num.split(' ')
    return int(num[0])

这比使用纯 Python 略快,但代价是内存使用量。子进程将在执行命令时分叉一个与父进程具有相同内存占用的新进程。

解决方案 13:

一行解决方案:

import os
os.system("wc -l  filename")  

我的片段:

>>> os.system('wc -l *.txt')

输出:

0 bar.txt
1000 command.txt
3 test_file.txt
1003 total

解决方案 14:

凯尔的回答

num_lines = sum(1 for line in open('my_file.txt'))

可能是最好的。另一种方法是:

num_lines =  len(open('my_file.txt').read().splitlines())

以下是两者的性能比较:

In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop

In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop

解决方案 15:

我通过这个版本获得了小小的(4-8%)改进,它重用了一个常量缓冲区,因此它应该可以避免任何内存或GC开销:

lines = 0
buffer = bytearray(2048)
with open(filename) as f:
  while f.readinto(buffer) > 0:
      lines += buffer.count('
')

您可以尝试改变缓冲区大小,也许会看到一点改善。

解决方案 16:

对我来说这个版本是最快的:

#!/usr/bin/env python

def main():
    f = open('filename')                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('
')
        buf = read_f(buf_size)

    print lines

if __name__ == '__main__':
    main()

原因:缓冲比逐行读取更快,而且string.count速度也很快

解决方案 17:

这段代码更短更清晰。这可能是最好的方法:

num_lines = open('yourfile.ext').read().count('
')

解决方案 18:

为了完成前面答案中的方法,我尝试了使用 fileinput 模块的变体:

import fileinput as fi   

def filecount(fname):
        for line in fi.input(fname):
            pass
        return fi.lineno()

并将一个 6000 万行的文件传递给前面答案中提出的所有方法:

mapcount:    6.13
simplecount: 4.59
opcount:     4.43
filecount:  43.3
bufcount:    0.171

令我有点惊讶的是,fileinput 如此糟糕并且比所有其他方法的扩展性都要差得多……

解决方案 19:

我对缓冲区大小进行了如下修改:

def CountLines(filename):
    f = open(filename)
    try:
        lines = 1
        buf_size = 1024 * 1024
        read_f = f.read # loop optimization
        buf = read_f(buf_size)

        # Empty file
        if not buf:
            return 0

        while buf:
            lines += buf.count('
')
            buf = read_f(buf_size)

        return lines
    finally:
        f.close()

现在也计算空文件和最后一行(没有 \n)。

解决方案 20:

已经有很多答案提供了很好的时间比较,但我相信他们只是通过查看行数来衡量性能(例如,Nico Schlömer 的出色图表)。

为了准确衡量绩效,我们应该关注:

  • 行数

  • 线条的平均大小

  • ... 最终文件的总大小(可能会影响内存)

首先,OP(带有for)的功能和功能sum(1 for line in f)表现得不太好......

好的竞争者是mmapbuffer

总结一下:根据我的分析(Windows 上带有 SSD 的 Python 3.9):

  1. 对于行数相对较短(100 个字符以内)的大文件:使用带缓冲区的函数buf_count_newlines_gen

def buf_count_newlines_gen(fname: str) -> int:
    """Count the number of lines in a file"""
    def _make_gen(reader):
        b = reader(1024 * 1024)
        while b:
            yield b
            b = reader(1024 * 1024)

    with open(fname, "rb") as f:
        count = sum(buf.count(b"
") for buf in _make_gen(f.raw.read))
    return count

  1. 对于可能包含较长行(最多 2000 个字符)的文件,忽略行数:使用带有 mmap 的函数:count_nb_lines_mmap

def count_nb_lines_mmap(file: Path) -> int:
    """Count the number of lines in a file"""
    with open(file, mode="rb") as f:
        mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
        nb_lines = 0
        while mm.readline():
            nb_lines += 1
        mm.close()
        return nb_lines
  1. 对于具有非常好的性能的短代码(特别是对于中等大小的文件):

def itercount(filename: str) -> int:
    """Count the number of lines in a file"""
    with open(filename, 'rb') as f:
        return sum(1 for _ in f)

以下是不同指标的摘要(timeit7 次运行,每次 10 次循环的平均时间):

功能文件小,行短文件较小,行较长文件大,行短文件很大,行很长文件越大,行数越短
... 尺寸 ...0.04 MB1.16 MB318 兆17 兆328 兆
... nb 行 ...915 行 < 100 个字符915 行 < 2000 个字符389000 行 < 100 个字符389,000 行 < 2000 个字符980 万行 < 100 个字符
count_nb_lines_blocks0.183 毫秒1.718 毫秒36.799 毫秒415.393 毫秒517.920 毫秒
count_nb_lines_mmap0.185 毫秒0.582 毫秒44.801 毫秒185.461 毫秒691.637 毫秒
buf_count_newlines_gen0.665 毫秒1.032 毫秒15.620 毫秒213.458 毫秒318.939 毫秒
itercount0.135 毫秒0.817 毫秒31.292 毫秒223.120 毫秒628.760 毫秒

count_nb_lines_mmap注意:我还对buf_count_newlines_gen一个 8 GB 的文件(包含 970 万行,每行超过 800 个字符)进行了比较。我们得到的平均结果为 5.39 秒,buf_count_newlines_gen而 为 4.2 秒count_nb_lines_mmap,因此对于行数较长的文件,后一个函数似乎确实更好。

下面是我使用的代码:

import mmap
from pathlib import Path

def count_nb_lines_blocks(file: Path) -> int:
    """Count the number of lines in a file"""

    def blocks(files, size=65536):
        while True:
            b = files.read(size)
            if not b:
                break
            yield b

    with open(file, encoding="utf-8", errors="ignore") as f:
        return sum(bl.count("
") for bl in blocks(f))


def count_nb_lines_mmap(file: Path) -> int:
    """Count the number of lines in a file"""
    with open(file, mode="rb") as f:
        mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
        nb_lines = 0
        while mm.readline():
            nb_lines += 1
        mm.close()
        return nb_lines


def count_nb_lines_sum(file: Path) -> int:
    """Count the number of lines in a file"""
    with open(file, "r", encoding="utf-8", errors="ignore") as f:
        return sum(1 for line in f)


def count_nb_lines_for(file: Path) -> int:
    """Count the number of lines in a file"""
    i = 0
    with open(file) as f:
        for i, _ in enumerate(f, start=1):
            pass
    return i


def buf_count_newlines_gen(fname: str) -> int:
    """Count the number of lines in a file"""
    def _make_gen(reader):
        b = reader(1024 * 1024)
        while b:
            yield b
            b = reader(1024 * 1024)

    with open(fname, "rb") as f:
        count = sum(buf.count(b"
") for buf in _make_gen(f.raw.read))
    return count


def itercount(filename: str) -> int:
    """Count the number of lines in a file"""
    with open(filename, 'rbU') as f:
        return sum(1 for _ in f)


files = [small_file, big_file, small_file_shorter, big_file_shorter, small_file_shorter_sim_size, big_file_shorter_sim_size]
for file in files:
    print(f"File: {file.name} (size: {file.stat().st_size / 1024 ** 2:.2f} MB)")
    for func in [
        count_nb_lines_blocks,
        count_nb_lines_mmap,
        count_nb_lines_sum,
        count_nb_lines_for,
        buf_count_newlines_gen,
        itercount,
    ]:
        result = func(file)
        time = Timer(lambda: func(file)).repeat(7, 10)
        print(f" * {func.__name__}: {result} lines in {mean(time) / 10 * 1000:.3f} ms")
    print()
File: small_file.ndjson (size: 1.16 MB)
 * count_nb_lines_blocks: 915 lines in 1.718 ms
 * count_nb_lines_mmap: 915 lines in 0.582 ms
 * count_nb_lines_sum: 915 lines in 1.993 ms
 * count_nb_lines_for: 915 lines in 3.876 ms
 * buf_count_newlines_gen: 915 lines in 1.032 ms
 * itercount: 915 lines in 0.817 ms

File: big_file.ndjson (size: 317.99 MB)
 * count_nb_lines_blocks: 389000 lines in 415.393 ms
 * count_nb_lines_mmap: 389000 lines in 185.461 ms
 * count_nb_lines_sum: 389000 lines in 485.370 ms
 * count_nb_lines_for: 389000 lines in 967.075 ms
 * buf_count_newlines_gen: 389000 lines in 213.458 ms
 * itercount: 389000 lines in 223.120 ms

File: small_file__shorter.ndjson (size: 0.04 MB)
 * count_nb_lines_blocks: 915 lines in 0.183 ms
 * count_nb_lines_mmap: 915 lines in 0.185 ms
 * count_nb_lines_sum: 915 lines in 0.251 ms
 * count_nb_lines_for: 915 lines in 0.244 ms
 * buf_count_newlines_gen: 915 lines in 0.665 ms
 * itercount: 915 lines in 0.135 ms

File: big_file__shorter.ndjson (size: 17.42 MB)
 * count_nb_lines_blocks: 389000 lines in 36.799 ms
 * count_nb_lines_mmap: 389000 lines in 44.801 ms
 * count_nb_lines_sum: 389000 lines in 59.068 ms
 * count_nb_lines_for: 389000 lines in 81.387 ms
 * buf_count_newlines_gen: 389000 lines in 15.620 ms
 * itercount: 389000 lines in 31.292 ms

File: small_file__shorter_sim_size.ndjson (size: 1.21 MB)
 * count_nb_lines_blocks: 36457 lines in 1.920 ms
 * count_nb_lines_mmap: 36457 lines in 2.615 ms
 * count_nb_lines_sum: 36457 lines in 3.993 ms
 * count_nb_lines_for: 36457 lines in 6.011 ms
 * buf_count_newlines_gen: 36457 lines in 1.363 ms
 * itercount: 36457 lines in 2.147 ms

File: big_file__shorter_sim_size.ndjson (size: 328.19 MB)
 * count_nb_lines_blocks: 9834248 lines in 517.920 ms
 * count_nb_lines_mmap: 9834248 lines in 691.637 ms
 * count_nb_lines_sum: 9834248 lines in 1109.669 ms
 * count_nb_lines_for: 9834248 lines in 1683.859 ms
 * buf_count_newlines_gen: 9834248 lines in 318.939 ms
 * itercount: 9834248 lines in 628.760 ms

解决方案 21:

如果想要在 Linux 中以低成本获取 Python 的行数,我推荐这种方法:

import os
print os.popen("wc -l file_path").readline().split()[0]

file_path 可以是抽象文件路径,也可以是相对路径。希望这能有所帮助。

解决方案 22:

这是对其他一些答案的元评论。

  1. 行读取和缓冲`
    计数技术不会对每个文件返回相同的答案,因为有些文本文件的最后一行末尾没有换行符。您可以通过检查最后一个非空缓冲区的最后一个字节并如果不是则加 1 来解决这个问题b'

'`。

  1. 在 Python 3 中,以文本模式和二进制模式打开文件会产生不同的结果,因为文本模式默认将 CR、LF 和 CRLF 识别为行尾(将它们全部转换为`'
    '),而在二进制模式下,如果您计数 ,则只会计算 LF 和 CRLF b'

'`。无论您按行读取还是读取固定大小的缓冲区,这都适用。经典的 Mac OS使用 CR 作为行尾;我不知道这些文件现在有多常见。

  1. 缓冲区读取方法使用与文件大小无关的有限数量的 RAM,而行读取方法在最坏的情况下可能会将整个文件一次性读入 RAM(尤其是当文件使用 CR 行结尾时)。在最坏的情况下,它可能使用的 RAM 远远超过文件大小,因为行缓冲区的动态调整以及(如果您以文本模式打开)Unicode 解码和存储会产生开销。

  2. readinto您可以通过预先分配字节数组并使用而不是 来改善缓冲方法的内存使用情况,并可能提高速度read。 现有答案之一(投票很少)这样做,但它有缺陷(它会重复计算一些字节)。

  3. 上面的缓冲区读取答案使用较大的缓冲区(1 MiB)。由于 OS 预读,使用较小的缓冲区实际上可以更快。如果您一次读取 32K 或 64K,OS 可能会在您请求之前开始将下一个 32K/64K 读入缓存,并且每次访问内核都会几乎立即返回。如果您一次读取 1 MiB,OS 不太可能推测性地读取整个兆字节。它可能会预读较少的量,但您仍将花费大量时间坐在内核中等待磁盘返回其余数据。

解决方案 23:

已经有很多答案,但不幸的是,其中大多数都只是针对难以优化的问题的微小经济......

我参与过几个项目,其中行数是软件的核心功能,并且尽快处理大量文件至关重要。

行数的主要瓶颈是 I/O 访问,因为您需要读取每一行才能检测行返回字符,这根本就没有办法解决。第二个潜在瓶颈是内存管理:一次加载的越多,处理速度就越快,但与第一个瓶颈相比,这个瓶颈可以忽略不计。

因此,除了禁用GC收集和其他微观管理技巧等微小优化之外,还有三种主要方法可以减少行计数函数的处理时间:

  1. 硬件解决方案:最主要、最明显的方式是非编程式的:购买速度非常快的 SSD/闪存硬盘。到目前为止,这是获得最大速度提升的方法。

  2. 数据预处理和行并行化:如果您生成或可以修改您处理的文件的生成方式,或者您可以接受对它们进行预处理。首先将行返回转换为Unix 样式(`
    `),因为与 Windows 相比,这将节省 1 个字符(节省的不是很多,但却很容易获得),其次,最重要的是,您可以编写固定长度的行。如果需要可变长度,则可以填充较小的行(如果长度变化不是很大)。这样,您可以立即从总文件大小中计算出行数,访问速度要快得多。此外,通过使用固定长度的行,您不仅通常可以预先分配内存以加快处理速度,而且还可以并行处理行!当然,并行化在具有比 HDD 快得多的随机访问 I/O 的闪存/SSD 磁盘上效果更好。通常,解决问题的最佳方法是对其进行预处理,以便它更适合您的最终目的。

  3. 磁盘并行化 + 硬件解决方案:如果您可以购买多个硬盘(如果可能的话,还可以购买 SSD 闪存盘),那么您甚至可以通过利用并行化来超越一个磁盘的速度,通过以平衡的方式(最简单的方法是按总大小平衡)在磁盘之间存储文件,然后从所有这些磁盘并行读取。然后,您可以期望获得与您拥有的磁盘数量成比例的乘数提升。如果购买多个磁盘不适合您,那么并行化可能不会有帮助(除非您的磁盘具有多个读取头,如某些专业级磁盘,但即使如此,磁盘的内部缓存和 PCB 电路也可能成为瓶颈并阻止您完全并行使用所有磁头,另外您必须为将要使用的硬盘设计一个特定的代码,因为您需要知道确切的集群映射,以便您将文件存储在不同磁头下的集群中,并且之后可以使用不同的磁头读取它们)。事实上,众所周知,顺序读取几乎总是比随机读取更快,并且单个磁盘上的并行化将具有与随机读取更相似的性能而不是顺序读取(例如,您可以使用CrystalDiskMark在两个方面测试您的硬盘速度)。

如果以上都不行,那么您只能依靠微观管理技巧来将行计数函数的速度提高几个百分点,但不要指望会有什么重大的进展。相反,您可以预料到,您花在调整上的时间与您将看到的速度改进回报是不成比例的。

解决方案 24:

print open('file.txt', 'r').read().count("
") + 1

解决方案 25:

使用 Numba

我们可以使用Numba将我们的函数 JIT(即时)编译为机器代码。def numbacountparallel(fname)运行速度比def file_len(fname)问题快 2.8 倍。

笔记:

由于我的电脑上没有太多磁盘活动,因此在运行基准测试之前,操作系统已经将文件缓存到内存中。第一次读取文件时,时间会慢得多,这使得使用 Numba 的时间优势微不足道。

第一次调用该函数时,JIT 编译需要额外的时间。

如果我们要做的不仅仅是计算行数,这将会很有用。

Cython是另一种选择。

结论

由于计数行数将受到 I/O 限制,因此请使用问题中的 def file_len(fname),除非您想做的不仅仅是计数行数。

import timeit

from numba import jit, prange
import numpy as np

from itertools import (takewhile,repeat)

FILE = '../data/us_confirmed.csv' # 40.6MB, 371755 line file
CR = ord('
')


# Copied from the question above. Used as a benchmark
def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


# Copied from another answer. Used as a benchmark
def rawincount(filename):
    f = open(filename, 'rb')
    bufgen = takewhile(lambda x: x, (f.read(1024*1024*10) for _ in repeat(None)))
    return sum( buf.count(b'
') for buf in bufgen )


# Single thread
@jit(nopython=True)
def numbacountsingle_chunk(bs):

    c = 0
    for i in range(len(bs)):
        if bs[i] == CR:
            c += 1

    return c


def numbacountsingle(filename):
    f = open(filename, "rb")
    total = 0
    while True:
        chunk = f.read(1024*1024*10)
        lines = numbacountsingle_chunk(chunk)
        total += lines
        if not chunk:
            break

    return total


# Multi thread
@jit(nopython=True, parallel=True)
def numbacountparallel_chunk(bs):

    c = 0
    for i in prange(len(bs)):
        if bs[i] == CR:
            c += 1

    return c


def numbacountparallel(filename):
    f = open(filename, "rb")
    total = 0
    while True:
        chunk = f.read(1024*1024*10)
        lines = numbacountparallel_chunk(np.frombuffer(chunk, dtype=np.uint8))
        total += lines
        if not chunk:
            break

    return total

print('numbacountparallel')
print(numbacountparallel(FILE)) # This allows Numba to compile and cache the function without adding to the time.
print(timeit.Timer(lambda: numbacountparallel(FILE)).timeit(number=100))

print('
numbacountsingle')
print(numbacountsingle(FILE))
print(timeit.Timer(lambda: numbacountsingle(FILE)).timeit(number=100))

print('
file_len')
print(file_len(FILE))
print(timeit.Timer(lambda: rawincount(FILE)).timeit(number=100))

print('
rawincount')
print(rawincount(FILE))
print(timeit.Timer(lambda: rawincount(FILE)).timeit(number=100))

每个函数调用 100 次的时间(以秒为单位)

numbacountparallel
371755
2.8007332000000003

numbacountsingle
371755
3.1508585999999994

file_len
371755
6.7945494

rawincount
371755
6.815438

解决方案 26:

简单方法:

  1. 方法 1

>>> f = len(open("myfile.txt").readlines())
>>> f

输出:

430
  1. 方法 2

>>> f = open("myfile.txt").read().count('
')
>>> f

输出:

430
  1. 方法 3

num_lines = len(list(open('myfile.txt')))

解决方案 27:

def count_text_file_lines(path):
    with open(path, 'rt') as file:
        line_count = sum(1 for _line in file)
    return line_count

解决方案 28:

大文件的另一种方法是使用xreadlines():

count = 0
for line in open(thefilepath).xreadlines(  ): count += 1

对于 Python 3,请参阅:Python 3 中什么替代了 xreadlines()?

解决方案 29:

打开文件的结果是一个迭代器,可以将其转换为序列,其长度为:

with open(filename) as f:
   return len(list(f))

这比您的显式循环更简洁,并且避免了enumerate

解决方案 30:

这可能有效:

import fileinput
import sys

counter = 0
for line in fileinput.input([sys.argv[1]]):
    counter += 1

fileinput.close()
print counter
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1259  
  IPD(Integrated Product Development)流程管理作为一种先进的产品开发管理理念和方法,在提升企业创新能力方面发挥着至关重要的作用。它打破了传统产品开发过程中部门之间的壁垒,通过整合资源、优化流程,实现产品的快速、高效开发,为企业在激烈的市场竞争中赢得优势。IPD流程管理的核心概念IPD流程...
IPD流程中PDCP是什么意思   11  
  IPD(Integrated Product Development)流程管理作为一种先进的产品开发管理模式,旨在通过整合各种资源,实现产品的高效、高质量开发。在这一过程中,团队协作无疑是成功的关键。有效的团队协作能够打破部门壁垒,促进信息共享,提升决策效率,从而确保产品开发项目顺利推进。接下来,我们将深入探讨IPD流...
IPD培训课程   9  
  IPD(Integrated Product Development)研发管理体系作为一种先进的产品开发理念和方法,在众多企业中得到了广泛应用。它旨在打破部门壁垒,整合资源,实现产品开发的高效、协同与创新。在项目周期方面,IPD研发管理体系有着深远且多维度的影响,深入剖析这些影响,对于企业优化产品开发流程、提升市场竞争...
华为IPD流程   11  
  IPD(Integrated Product Development)流程管理是一种先进的产品开发管理模式,旨在通过整合企业的各种资源,实现产品的高效、高质量开发。它涵盖了从产品概念提出到产品退市的整个生命周期,对企业的发展具有至关重要的意义。接下来将详细阐述IPD流程管理的五个阶段及其重要性。概念阶段概念阶段是IPD...
IPD概念阶段   12  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用