百万级嵌套JSON文件对比性能优化请求(单文件4GB)
大JSON文件对比脚本优化方案
优化后的代码
import json import time import logging import argparse import orjson from multiprocessing import Pool, cpu_count from itertools import zip_longest # 配置日志 def setup_logger(log_file): logger = logging.getLogger() logger.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') # 移除默认处理器避免重复输出 if logger.handlers: for handler in logger.handlers: logger.removeHandler(handler) # 文件处理器 file_handler = logging.FileHandler(log_file, mode='w', encoding='utf-8') file_handler.setFormatter(formatter) logger.addHandler(file_handler) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) logger.addHandler(console_handler) return logger execution_times = {} def time_function(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) elapsed_time = time.time() - start_time execution_times[func.__name__] = elapsed_time return result return wrapper def parse_single_line(line): """用orjson解析单条JSON记录,速度远快于标准json库""" try: return orjson.loads(line.strip()) except orjson.JSONDecodeError as e: logging.error(f"解析JSON行失败: {e}") return None @time_function def parse_json_file(file_path): """逐行读取并解析JSON文件,返回生成器避免内存占用""" with open(file_path, 'r', encoding='utf-8') as f: for line in f: record = parse_single_line(line) if record is not None: yield record def compare_records_batch(batch): """批量对比记录,用于多进程处理""" batch_diffs = [] for idx, (record1, record2) in batch: diffs = compare_json_records(record1, record2) if diffs: batch_diffs.append({"record_index": idx, "differences": diffs}) return batch_diffs @time_function def compare_json_records(record1, record2): """迭代式对比嵌套JSON,比递归更高效""" differences = {} stack = [("root", record1, record2)] while stack: path, val1, val2 = stack.pop() # 处理字典类型 if isinstance(val1, dict) and isinstance(val2, dict): all_keys = val1.keys() | val2.keys() for key in all_keys: stack.append((f"{path}.{key}", val1.get(key), val2.get(key))) continue # 处理列表类型(仅当长度相同时逐元素对比) if isinstance(val1, list) and isinstance(val2, list): if len(val1) != len(val2): differences[path] = {"old": val1, "new": val2} continue for i, (item1, item2) in enumerate(zip(val1, val2)): stack.append((f"{path}[{i}]", item1, item2)) continue # 基础类型对比 if val1 != val2: differences[path] = {"old": val1, "new": val2} return differences @time_function def compare_large_json_files(file1, file2, batch_size, output_file, log_file): logger = setup_logger(log_file) logger.info(f"开始对比文件: {file1} 和 {file2}") # 生成两个文件的记录迭代器 records1 = parse_json_file(file1) records2 = parse_json_file(file2) # 准备带索引的批量任务 task_batches = [] current_batch = [] for record_idx, (r1, r2) in enumerate(zip_longest(records1, records2)): # 处理记录数不一致的情况 if r1 is None: current_batch.append((record_idx, (None, r2))) elif r2 is None: current_batch.append((record_idx, (r1, None))) else: current_batch.append((record_idx, (r1, r2))) if len(current_batch) >= batch_size: task_batches.append(current_batch) current_batch = [] if current_batch: task_batches.append(current_batch) logger.info(f"共生成 {len(task_batches)} 个处理批次,每个批次 {batch_size} 条记录") # 多进程处理,进程数设为CPU核心数 start_time = time.time() total_diffs = [] with Pool(processes=cpu_count()) as pool: for batch_result in pool.imap_unordered(compare_records_batch, task_batches): total_diffs.extend(batch_result) # 按记录索引排序差异结果 total_diffs.sort(key=lambda x: x["record_index"]) # 保存差异结果 with open(output_file, 'w', encoding='utf-8') as f: # 用orjson序列化更快,输出兼容标准JSON f.write(orjson.dumps(total_diffs, option=orjson.OPT_INDENT_2).decode('utf-8')) total_time = time.time() - start_time logger.info(f"对比完成,总耗时: {total_time:.2f} 秒") logger.info(f"共发现 {len(total_diffs)} 条差异记录") logger.info(f"差异结果已保存至: {output_file}") @time_function def main(): parser = argparse.ArgumentParser(description="高效对比两个大JSON文件(每行一条JSON记录)") parser.add_argument("file1", help="第一个JSON文件路径") parser.add_argument("file2", help="第二个JSON文件路径") parser.add_argument("--batch_size", type=int, default=2000, help="每个进程处理的记录批次大小,默认2000") parser.add_argument("--output_file", required=True, help="差异结果输出文件路径") parser.add_argument("--log_file", required=True, help="日志文件路径") args = parser.parse_args() compare_large_json_files(args.file1, args.file2, args.batch_size, args.output_file, args.log_file) # 输出各函数执行时间 logger = logging.getLogger() logger.info("\n各函数执行时间统计:") for func_name, elapsed in execution_times.items(): logger.info(f"{func_name}: {elapsed:.4f} 秒") print("\n各函数执行时间统计:") for func_name, elapsed in execution_times.items(): print(f"{func_name}: {elapsed:.4f} 秒") if __name__ == "__main__": main()
核心优化点
移除预格式化步骤
原代码需要生成4GB级别的中间JSON数组文件,既耗时又占磁盘空间。优化后直接逐行解析原始文件,完全规避中间文件的IO开销。替换解析库为orjson
orjson是Rust实现的JSON库,解析/序列化速度是标准json库的5-10倍,大幅降低文件解析的时间占比。对比函数改为迭代实现
原递归对比存在大量Python函数调用开销,改为基于栈的迭代遍历,减少函数调用次数,提升对比效率。高效多进程处理
- 进程数设置为CPU核心数,最大化利用多核资源
- 批量提交任务而非单条提交,减少进程间通信(IPC)开销,解决之前多进程效率低下的问题
- 使用
imap_unordered异步获取结果,避免等待单个批次完成,提升整体处理速度
内存优化
全程使用生成器读取记录,避免一次性加载百万条记录到内存,降低内存占用,避免系统换页导致的性能下降。新增记录数不一致处理
自动识别两个文件记录数不匹配的情况,并将缺失记录标记为差异。
使用说明
- 安装依赖:
pip install orjson - 运行示例:
python compare_json.py old_system_output.json new_system_output.json --batch_size 2000 --output_file diffs.json --log_file compare.log - 批次大小建议:根据CPU核心数调整,核心数越多可适当增大(如4核用2000,8核用4000)
内容的提问来源于stack exchange,提问作者Prakash Kumar
相关产品推荐
相关产品推荐

