在处理 GB 级应用日志时,常见需求是从中快速提取 ERROR 或特定 trace_id。原文以 2GB 左右日志为案例,对比 Python 的逐行流式读取、mmap 内存映射和多进程分块并行三种过滤方案,并给出实测耗时与内存数据。下面按代码实现、边界处理和性能结果重新整理。
一、逐行流式读取:baseline
最直接的写法是 open() 配合 for line in f,逐行扫描,把包含关键字的行写入目标文件。逻辑简单可靠,适合日志规模不大或对速度不敏感的场景。示例:
- def filter_stream(log_path, keyword, out_path):
- with open(log_path, 'r', encoding='utf-8', errors='ignore') as fin, open(out_path, 'w') as fout:
- for line in fin:
- if keyword in line:
- fout.write(line)
复制代码
每次迭代都会在 Python 层做字符串解码和关键字匹配。原文实测 2GB 日志耗时约 45 秒,内存占用约 20MB,主要来自缓冲。问题是文件越大,Python 层循环和匹配开销越明显。
二、mmap + 内存视图:减少系统调用和 Python 循环
优化思路是用 mmap 把文件映射到虚拟内存,避免频繁 read 系统调用;再通过 bytes.find() 定位关键字,并配合 memoryview 切片输出匹配行。核心示例:
- import mmap
- def filter_mmap(log_path, keyword, out_path):
- keyword_b = keyword.encode()
- with open(log_path, 'rb') as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm, open(out_path, 'wb') as fout:
- start = 0
- while True:
- idx = mm.find(keyword_b, start)
- if idx == -1:
- break
- # 找到行首和行尾
- line_start = mm.rfind(b'\n', 0, idx) + 1
- line_end = mm.find(b'\n', idx)
- if line_end == -1:
- line_end = mm.size()
- fout.write(mm[line_start:line_end])
- fout.write(b'\n')
- start = line_end + 1
复制代码
关键点是 mmap.find 由 C 实现,速度远高于 Python 层循环。需要注意边界情况,例如关键字跨行、最后一行没有换行符等。原文实测 2GB 日志耗时 12.8 秒,内存 45MB,内存开销主要来自映射。
三、多进程分块并行:利用多核的终极优化
当单机多核时,可以按字节偏移把文件均分成 N 块,每个进程独立处理一块,最后合并结果。为了避免切块时截断行,需要查找块首的换行符进行对齐。原文给出简化示例:
- import os, multiprocessing as mp
- def worker(chunk_path, start, end, keyword, out_path):
- with open(chunk_path, 'rb') as f:
- f.seek(start)
- # 对齐到行首(略)
- data = f.read(end - start)
- lines = data.split(b'\n')
- matched = [line for line in lines if keyword in line]
- with open(out_path, 'ab') as fout:
- for line in matched:
- fout.write(line + b'\n')
- def parallel_filter(log_path, keyword, num_workers=4):
- size = os.path.getsize(log_path)
- chunk_size = size // num_workers
- # 创建任务列表(略)
复制代码
实际应用时要处理块边界,可用 seek 定位到下一个换行符。并行方案在 8 核机器上可把 2GB 日志处理时间从 45 秒降到 8 秒。原文还给出并行 mmap(4 进程)耗时 8.1 秒、内存峰值 180MB 的数据。进程数需要控制,否则可能耗尽内存。
四、性能验证与方案选择
原文测试环境为 8 核 CPU、16GB 内存、2.1GB 日志、约 1200 万行。对比结果如下:
流式读取:45.2 秒,内存 18MB
mmap 方案:12.8 秒,内存 45MB(映射开销)
并行 mmap(4 进程):8.1 秒,内存 180MB(峰值)
验证方法:使用 time 命令测耗时,用 /usr/bin/time -v 查看最大常驻内存。选择建议:单机日志过滤优先考虑 mmap,性价比最高;如果追求极致速度且内存充足,可以上多进程;最终应结合日志大小和机器规格决定。并行时尤其要限制进程数,避免内存耗尽。 |