减少运行时间,文件读取,每一行的字符串操作和文件写入

时间:2019-03-18 17:21:57

标签: python python-3.x python-asyncio

我正在写一个脚本,该脚本从多个文件中读取所有行,在每个块的开头读取一个数字,然后将该数字放在该块的每一行的前面,直到下一个数字,依此类推。然后,它将所有读取的行写入单个.csv文件。

我正在读取的文件如下:

13368:
2385003,4,2004-07-08
659432,3,2005-03-16
13369:
751812,2,2002-12-16
2625420,2,2004-05-25

输出文件应如下所示:

13368,2385003,4,2004-07-08
13368,659432,3,2005-03-16
13369,751812,2,2002-12-16
13369,2625420,2,2004-05-25

当前我的脚本是这个

from asyncio import Semaphore, ensure_future, gather, run
import time

limit = 8

async def read(file_list):
    tasks = list()
    result = None

    sem = Semaphore(limit)

    for file in file_list:
        task = ensure_future(read_bounded(file,sem))
        tasks.append(task)

        result = await gather(*tasks)

    return result

async def read_bounded(file,sem):
    async with sem:
        return await read_one(file)

async def read_one(filename):
    result = list()
    with open(filename) as file:
        dataList=[]
        content = file.read().split(":")
        file.close()
        j=1
        filmid=content[0]
        append=result.append
        while j<len(content):
            for entry in content[j].split("\n"):
                if len(entry)>10:
                    append("%s%s%s%s" % (filmid,",",entry,"\n"))
                else:
                    if len(entry)>0:
                        filmid=entry
            j+=1
    return result

if __name__ == '__main__':
    start=time.time()
    write_append="w"
    files = ['combined_data_1.txt', 'combined_data_2.txt', 'combined_data_3.txt', 'combined_data_4.txt']

    res = run(read(files))

    with open("output.csv",write_append) as outputFile:
        for result in res:
            outputFile.write(''.join(result))
            outputFile.flush()
    outputFile.close()
    end=time.time()
    print(end-start)

它的运行时间约为135秒(读取的4个文件每个都大小为500MB,输出文件为2.3GB)。运行脚本大约需要10GB的RAM。我认为这可能是个问题。 我认为,创建所有行的列表需要最长时间。 我想减少该程序的运行时间,但是我是python的新手,不确定如何执行此操作。你能给我一些建议吗?

谢谢

编辑:

我在cmd中测量了以下命令的时间(我的计算机上仅安装了Windows,因此希望使用等效的cmd-Commands):

顺序写入NUL

timecmd "type combined_data_1.txt combined_data_2.txt combined_data_3.txt combined_data_4.txt  > NUL"

combined_data_1.txt


combined_data_2.txt


combined_data_3.txt


combined_data_4.txt

command took 0:1:25.87 (85.87s total)

顺序写入文件

timecmd "type combined_data_1.txt combined_data_2.txt combined_data_3.txt combined_data_4.txt  > test.csv"

combined_data_1.txt
combined_data_2.txt
combined_data_3.txt
combined_data_4.txt

command took 0:2:42.93 (162.93s total)

平行

timecmd "type combined_data_1.txt > NUL & type combined_data_2.txt > NUL & type combined_data_3.txt >NUL & type combined_data_4.txt  > NUL"
command took 0:1:25.51 (85.51s total)

3 个答案:

答案 0 :(得分:2)

在这种情况下,由于两个原因,您无法通过使用asyncio来获得任何收益:

  • asyncio是单线程的,不会并行处理(在Python中,neither can threads
  • IO调用访问文件系统,而asyncio并不涵盖该文件系统-它主要是关于网络IO

您没有正确使用asyncio的礼物是您的read_one协程不包含单个await。这意味着它永远不会暂停执行,并且会在完全屈服于另一个协程之前运行完成。使其成为普通函数(并完全删除异步)将具有完全相同的结果。

这是脚本的重写版本,具有以下更改:

    整个
  • 字节IO,以提高效率
  • 遍历文件而不是一次全部加载
  • 顺序代码
import sys

def process(in_filename, outfile):
    with open(in_filename, 'rb') as r:
        for line in r:
            if line.endswith(b':\n'):
                prefix = line[:-2]
                continue
            outfile.write(b'%s,%s' % (prefix, line))

def main():
    in_files = sys.argv[1:-1]
    out_file = sys.argv[-1]
    with open(out_file, 'wb') as out:
        for fn in in_files:
            process(fn, out)

if __name__ == '__main__':
    main()

在我的机器和Python 3.7上,此版本的执行速度约为22 s / GiB,并在四个随机生成的文件(每个文件包含550 MiB)上进行了测试。它的内存占用量可以忽略不计,因为它永远不会将整个文件加载到内存中。

该脚本在Python 2.7上不变运行,其时钟为27 s / GiB。 Pypy(6.0.0)的运行速度更快,仅需11 s / GiB。

从理论上讲,concurrent.futures应该允许一个线程在另一个线程等待IO时进行处理,但最终结果比最简单的顺序方法要慢得多。

答案 1 :(得分:1)

您要读取2 GiB并写入2 GiB,且耗时短且内存消耗低。 对于核心和主轴而言,并行性至关重要。 理想情况下,您将倾向于让他们都忙。 我假设您至少有四个核心可用。 分块您的I / O很重要,以避免过多的malloc分配。

从最简单的事情开始。 请进行一些测量,并更新您的问题以将其包括在内。

顺序

请依次测量

$ cat combined_data_[1234].csv > /dev/null

$ cat combined_data_[1234].csv > big.csv

我假设您的CPU使用率较低,因此将测量读写I / O速率。

平行

请进行并行I / O测量:

cat combined_data_1.csv > /dev/null &
cat combined_data_2.csv > /dev/null &
cat combined_data_3.csv > /dev/null &
cat combined_data_4.csv > /dev/null &
wait

这将让您知道重叠的读取是否可以提速。 例如,将文件放在四个不同的物理文件系统上可能会允许这样做–您将使四个主轴忙。

异步

根据这些时间,您可以选择放弃异步I / O,而是派生四个单独的python解释器。

逻辑

        content = file.read().split(":")

这是您大量内存占用的来源。 而不是一次拖拽整个文件,而是考虑按行或按块读取。 生成器可能为此提供了便捷的API。

编辑:

压缩

似乎您受I / O约束-在磁盘上等待时有空闲周期。 如果输出文件的最终使用者愿意进行解压缩,则 考虑使用gzipxz/lzmasnappy。 这个想法是,大部分经过的时间都花在了I / O上,因此您想操纵较小的文件来减少I / O。 当编写2 GiB的输出时,这将使您的脚本受益, 也会使使用该输出的代码受益。

作为单独的项目,您可能会安排产生四个输入文件的代码来产生它们的压缩版本。

答案 2 :(得分:0)

我已尝试解决您的问题。如果没有任何特殊库的先验知识,我认为这是一种非常简单的方法。

我刚取了两个名为input.txtinput2.txt的输入文件,内容如下。

注意:所有文件都在同一目录中。

  

input.txt

13368:
2385003,4,2004-07-08
659432,3,2005-03-16
13369:
751812,2,2002-12-16
2625420,2,2004-05-25
  

input2.txt

13364:
2385001,5,2004-06-08
659435,1,2005-03-16
13370:
751811,2,2023-12-16
2625220,2,2015-05-26

我已经以模块化方式编写了代码,以便您可以轻松地在项目中导入和使用它。使用python3 csv_writer.py从终端运行以下代码后,它将读取列表file_names中提供的所有文件,并生成output.csv将成为您要查找的结果。

  

csv_writer.py

# https://stackoverflow.com/questions/55226823/reduce-runtime-file-reading-string-manipulation-of-every-line-and-file-writing
import re

def read_file_and_get_output_lines(file_names):
    output_lines = []

    for file_name in file_names:
        with open(file_name) as f:
            lines = f.readlines()
            for new_line in lines:
                new_line = new_line.strip()

                if not re.match(r'^\d+:$', new_line):
                    output_line = [old_line]
                    output_line.extend(new_line.split(","))
                    output_lines.append(output_line)
                else:
                    old_line = new_line.rstrip(":")

    return output_lines

def write_lines_to_csv(output_lines, file_name):
    with open(file_name, "w+") as f:
        for arr in output_lines:
            line = ",".join(arr)
            f.write(line + '\n')

if __name__ == "__main__":
    file_names = [
        "input.txt",
        "input2.txt"
    ]

    output_lines = read_file_and_get_output_lines(file_names)
    print(output_lines)
    # [['13368', '2385003', '4', '2004-07-08'], ['13368', '659432', '3', '2005-03-16'], ['13369', '751812', '2', '2002-12-16'], ['13369', '2625420', '2', '2004-05-25'], ['13364', '2385001', '5', '2004-06-08'], ['13364', '659435', '1', '2005-03-16'], ['13370', '751811', '2', '2023-12-16'], ['13370', '2625220', '2', '2015-05-26']]

    write_lines_to_csv(output_lines, "output.csv")
  

output.csv

13368,2385003,4,2004-07-08
13368,659432,3,2005-03-16
13369,751812,2,2002-12-16
13369,2625420,2,2004-05-25
13364,2385001,5,2004-06-08
13364,659435,1,2005-03-16
13370,751811,2,2023-12-16
13370,2625220,2,2015-05-26