Python 性能优化|多线程读取文件

分析结论

多进程可以实现逐行遍历同一个文件(可以保证每一个进程遍历的行都是完整且互不重复的),且可以提高遍历性能。

多进程 / 多线程遍历文件速度
  1. 单进程、多线程读取同一个文件时,每个线程的运行时间并不能随线程数的增加的降低;
  2. 多进程读取同一个文件时,每个进程的运行时间随线程数的增加而降低。
进一步优化方法

通过统计读取到的字符串长度,计算当前文件指针位置,从而避免在每次遍历中均需使用 file.tell() 获取当前文件指针位置。

分析过程

构造 11202 MB、691130 行的测试数据。具体测试数据特征如下:

  • 文件大小:11746098941 Bytes(11202 MB)
  • 行数:691130
import time
1. 单进程、单线程遍历文件
t1 = time.time()
with open(path, "r", encoding="UTF-8") as file:
    for _ in file:
        pass
t2 = time.time()
print(t2 - t1)

运行时间:21.79 秒(三次测试 23.55、20.84、21.00 取平均值)

2. 多线程遍历文件
import os
from theading import Thread

定义每个线程 / 进程的遍历函数如下:通过捕获 UnicodeDecodeError 异常,避免出现刚好切分到半个字的情况;通过在遍历前先 file.readline(),使每一个切分点中尚未结束的行一定归属上一进程 / 线程而不是下一进程 / 线程,从而保证每一个进程 / 线程遍历的每一行都是完整且互不重复的。

def read(path, start, end):
    """每个进程 / 线程的遍历函数

    Parameters
    ----------
    path : str
        文件路径
    start : int
        本分块的开始位置
    end : int
        本分块的结束位置
    """
    cnt = 0
    with open(path, "r", encoding="UTF-8") as file:
        file.seek(start)  # 移动到目标位置
        if start != 0:
            while True:
                try:
                    file.readline()  # 跳过当前行(所有未遍历完的行属于上一段)
                    break
                except UnicodeDecodeError:  # 刚好切分到半个字,向后移动一个字符
                    file.seek(start + 1)
        while file.tell() <= end:
            file.readline()
            cnt += 1

定义多线程遍历函数:

def multi_thread_load_csv(path: str, n_thread: int):
    """多线程遍历函数"""
    size = os.path.getsize(path)  # 获取文件大小用于分块
    thread_lst = []
    for i in range(n_thread):
        s = size // n_thread * i  # 计算当前分块开始位置
        e = size // n_thread * (i + 1)  # 计算当前分块结束位置
        thread = Thread(target=read, args=(s, e))
        thread.start()
        thread_lst.append(thread)

    for thread in thread_lst:
        thread.join()

测试线程数为 1 - 10 之间的读取时间,每种线程数测试 10 次,测试代码及结果如下:

import numpy as np

for k in range(1, 11):
    use_time = []
    for _ in range(10):
        t1 = time.time()
        multi_thread_load_csv("/home/txjiang/archive_analysis/gather_detail.txt", k)
        t2 = time.time()
        use_time.append(t2 - t1)
        print(f"线程={k} 平均时间={np.average(use_time)} 标准差={np.std(use_time)}")
线程数用时的平均值用时的标准差
149.08410.6299
253.21891.4267
353.52901.3273
456.49231.4843
556.66793.2745
656.51641.7789
758.23521.1148
858.23530.6817
960.98961.3365
1064.20632.3251

因为在每一行的遍历中需要增加一次 file.tell(),所以单线程时的速度相较于直接读取会更慢。

3. 多进程遍历文件
from multiprocessing import Process

定义多进程遍历函数:

def multi_process_load_csv(path: str, n_process: int):
    """多进程遍历函数"""
    size = os.path.getsize(path)  # 获取文件大小用于分块
    process_lst = []
    for i in range(n_process):
        s = size // n_process * i  # 计算当前分块开始位置
        e = size // n_process * (i + 1)  # 计算当前分块结束位置
        process = Process(target=read, args=(s, e))
        process.start()
        process_lst.append(process)
    for process in process_lst:
        process.join()

测试线程数为 1 - 10 之间的读取时间,每种线程数测试 10 次,测试代码及结果如下:

import numpy as np

for k in range(1, 11):
    use_time = []
    for _ in range(10):
        t1 = time.time()
        multi_process_load_csv("/home/txjiang/archive_analysis/gather_detail.txt", k)
        t2 = time.time()
        use_time.append(t2 - t1)
        print(f"线程={k} 平均时间={np.average(use_time)} 标准差={np.std(use_time)}")
进程数用时的平均值用时的标准差
150.15610.8431
226.50890.5581
317.76630.2771
413.43380.3024
510.76540.2950
69.19500.3471
77.71600.1764
87.03210.1938
96.34840.2150
105.63540.1271
115.12830.2361
124.78410.0512
134.51490.2186
144.15250.0533
153.95540.1442
163.84810.1167
173.64550.0763
183.40300.0255
193.37320.2159
203.19330.0674
213.00910.0845
222.92350.0646
232.94740.2234
242.75000.0382
252.65920.0340
262.56870.0333
272.62730.3457
282.43430.0253
292.36470.0223
302.25720.0343

因为在每一行的遍历中需要增加一次 file.tell(),所以单进程时的速度相较于直接读取会更慢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值