一 前言

最近在开发的数据核对方案中用到了Python标准库Difflib,本来它工作的挺符合预期,可当它遇到那个文件,仿佛遇到了克星,那文件才100行*77列的数据,经它对比,居然耗时61s。这是无法接受的,因为后续线上流量抽取比对,绝非这点量级。该怎么破?

 

二 重现现象

以下是使用Difflib比对那个文件,数据量是100行*77列,耗时61s,如下:

Python标准库Difflib有坑? —— 两个方法快速提升数据对比效率_第1张图片

好吧,那就降低数据量到5行*77列,看看效果,耗时只有0.05s,如下:

Python标准库Difflib有坑? —— 两个方法快速提升数据对比效率_第2张图片


从耗时结果上,不难发现,Difflib在这个文件的比对性能较差,而且耗时不随数据量线性增加,这是最恐怖的地方,如果继续增大数据量,耗时将会变得无法忍受。

 

三 优化思路

Difflib作为标准库,它的功能只是比对数据,然后生成各样结果格式。当遇到耗时严重这类问题,首先应该从自己的数据上入手,我的优化思路有两个:

第一,    过滤掉相同的行数据,降低比对数据量;

第二,    数据分片;

针对第一个思路,将文件以行分割存放到列表,然后将列表相同位置的相同数据删除掉,只剩下不同的行数据,这样做的好处很明显,一方面可以降低比对的数据量,提升效率,另外,输出的结果也更干净,不会再输出无必要的相同行数据;

针对第二个思路,将待比对的数据划分成一个个相对较小的数据块,实现快速比对,这个方法的可行性,可以从上述数据量耗时比对得出。

 

四 具体实现

过滤相同行优化策略,实现代码如下:

            # 过滤相同行
source_length = len(source)  # source为原始数据按行分割的列表
target_length = len(target)  # target为目标数据按行分割的列表
min_length = source_length if source_length < target_length else target_length 
pos_list = []  # 标记相同行的行号,保留列头
for index in range(1, min_length):
    # 注意保序
    if operator.eq(source[index], target[index]):
        pos_list.append(index)
# 删除相同行数据, 注意索引漂移
source = [source[index] for index in range(source_length) if index not in pos_list]
target = [target[index] for index in range(target_length) if index not in pos_list]
          


数据分片优化策略,实现代码如下:

            # 分片
max_length = source_length if source_length > target_length else target_length # 用于分片
# 分片,注意保证不能漏行
start_pos = 0
step = 10  # 分片大小,即单次比对行数,默认10行
end_pos = start_pos + step
diff = difflib.HtmlDiff()  # 创建htmldiff实例对象
while end_pos < max_length + step:
    detail_info = diff.make_file(source[start_pos: end_pos], target[start_pos: end_pos])
    # 处理逻辑
          

 

五 优化结果

在仅使用数据分片的优化策略的情况下,比对那个100行*77列的文件,结果显示比对耗时仅1.8s。而正如上文所述,优化前比对该文件的耗时为61s,更重要的是,因为数据分片,每片比对耗时基本稳定,即使数据量继续增大,耗时也只是线性增加,而不再是类似指数型增加。另外,如果叠加过滤数据的第一种策略,相信随着数据量的下降,耗时数据会有更好的表现。但为了更直观的比对相同数据量下优化前后的效果,所以,在此只是使用数据分片的策略。

优化后耗时结果如下:

Python标准库Difflib有坑? —— 两个方法快速提升数据对比效率_第3张图片

 

六 其他

本文针对Python标准库Difflib比对文件时遇到的耗时严重问题,提出了两种优化策略,并经测试验证有效,即仅在数据分片策略下,同样文件的比对耗时从原先的61s降到1.8s,且耗时只是线性增加。如果有更好的方法,欢迎留言、交流。

关于python学习、分享、交流,笔者开通了微信公众号 【小蟒社区】 ,感兴趣的朋友可以关注下,欢迎加入,建立属于我们自己的小圈子,一起学python。