昨天在工作时遇到一个小问题,同事的某API返回太慢了,我帮他优化下。简单的探究了下需求,发现了一点得优化的地方。
需求
需求很简单,就是对数据结构进行初步的整理。a.json内容大致如下:
b.json内容大致如下(大概16M的一个大文件):
想要的结果如下:
就是把sent_id和received_id的值去b.json里查找对应的itemid,然后在原始的a.json的结构中增加一项data列,记录b中的值。
在开始鼓捣之前,我们需要先了解下,在Python中,列表是一种非常重要的数据结构,特性有点像数据结构中的链表。列表结构简单,容易扩展,但是却有一个比较致命的弱点——查找元素比较慢。完全就是看命的操作,查找的元素是第一个还好,万一是最后一个,那就要全部遍历完才知道是否有对应的元素。
还有另外一种同样重要的数据结构,被称作字典。字典的特点就是查找很快,很快很快的那种。
原始实现
实现起来其实很容易,就是循环判断嘛,按照常规的思路,可能会这样实现:
for item_dict in a_list: re_data = [] se_data = [] for value_dict in b_list: if item_dict.get('received_id') == value_dict['itemid']: re_data.append(value_dict) elif item_dict.get('sent_id') == value_dict['itemid']: se_data.append(value_dict)
优化
老夫掐指一算,看出来几个这段代码中制约性能的因素:
- 双循环增加时间复杂度
- b_list非常大,查找它恐怕不会是比较理想的解决方案。再加上它外面还有个循环,简直就是雪上加霜了
由于b.json是个大列表,列表里还有字典,因此不仅导致多了一层循环,还增大了列表不适合查找的特性。因此正确的解决思路是,将b.json的格式转换成字典,key对应item_id,值是一个列表,列表中的每个元素为字典。
由于我们并不预先知道字典的key是什么,因此只能动态的创建字典,使得它的值为空列表,然后追加数据。
完成这个操作大概有三个方法:
方法1:跑两个循环
result = {} for item in item_list: result[item['itemid']] = [] for item in item_list: result[item['itemid']].append(item)
思路很简单,时间复杂度也是线性的,可以接受
方法2:使用defaultDict
我们使用defaultDict定义字典,这样当访问不存在的key时返回列表,直接append就可以了。当然了要从collections里import这个好东西
from collections import defaultdict d = defaultdict(list) for item in item_list: d[item['itemid']].append(item)
方法3:利用setdefault
利用字典的setdefault方法,如果key不存在,我们可以让他返回个空列表,然后append呗~
result = {} for item in item_list: result.setdefault(item['itemid'], []).append(item)
这三者性能上,肯定是方法1稍微差一点啦,至于2和3,2比3稍好一点的。
这样的字典创造出来之后,后面的事情就很好处理了
for item in a_list: item.setdefault("data", []).extend(d.get(item.get('received_id'), [])) item.setdefault("data", []).extend(d.get(item.get('sent_id'), []))
由于a.json的数据不纯,有些元素没有received_id,因此只能用get方法了。要不用[]直接取数据速度应该会再快点的。
优化结构之前:
优化之后:
大概提升了30倍吧。
还需要更快?
这个时候除了换个解释器(如PyPy),提升配置,其实还有一个办法!那就是Cython。如果你用的是Anaconda或者是Linux,那就好办多了,那可真是太幸福了。但是Windows……
安装编译环境
要么就用mingw,要么就用visual c++ build tools,以后者为例……
根据你的Python 版本选择不同的build tools版本,
Python 3.6 对应 VS2015
Python 3.7 对应 VS2017
我就直接安装Visual Studio Build Tools 2017
安装时选择如下组件就可以了
- Visual C++ Build tools core features.
- VC++ 2017 v141 toolset (x86,x64)
- Visual C++ 2017 Redistributable Update
- Windows 10 SDK (10.0.16299.0) for Desktop C++
安装Cython
pip install cython
然后新建一个pyx文件,写代码……
再新建一个setup.py
from distutils.core import setup from Cython.Build import cythonize setup( name='Hello pyx', ext_modules=cythonize('hello.pyx') )
然后运行 python setup.py build
之后把build/lib下的那个pyd(Linux为so)拷贝走,就可以import了……
参考资料
https://matthew-brett.github.io/pydagogue/python_msvc.html
https://www.cnblogs.com/freeweb/p/6548208.html