我正在写一个脚本,该脚本从多个文件中读取所有行,在每个块的开头读取一个数字,然后将该数字放在该块的每一行的前面,直到下一个数字,依此类推。然后,它将所有读取的行写入单个.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)
答案 0 :(得分:2)
在这种情况下,由于两个原因,您无法通过使用asyncio
来获得任何收益:
您没有正确使用asyncio的礼物是您的read_one
协程不包含单个await
。这意味着它永远不会暂停执行,并且会在完全屈服于另一个协程之前运行完成。使其成为普通函数(并完全删除异步)将具有完全相同的结果。
这是脚本的重写版本,具有以下更改:
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约束-在磁盘上等待时有空闲周期。 如果输出文件的最终使用者愿意进行解压缩,则 考虑使用gzip,xz/lzma或snappy。 这个想法是,大部分经过的时间都花在了I / O上,因此您想操纵较小的文件来减少I / O。 当编写2 GiB的输出时,这将使您的脚本受益, 也会使使用该输出的代码受益。
作为单独的项目,您可能会安排产生四个输入文件的代码来产生它们的压缩版本。
答案 2 :(得分:0)
我已尝试解决您的问题。如果没有任何特殊库的先验知识,我认为这是一种非常简单的方法。
我刚取了两个名为input.txt
和input2.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