网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
时间戳 表名 联合 执行时间(毫秒)
这个问题的根源是,Django中的查询是惰性的。这意味着在你真正需要获取数据之前它不会访问数据库。同时,它只获取你指定的数据,如果需要其他附加数据,则要另外发出请求。
这正是本例程所遇到的情况。当通过House.objects.filter(country=country)来获得查询集时,Django将获取特定地区的所有房屋。但是,在序列化一个house实例时,HouseSerializer需要房子的country实例来计算序列化器的country字段。由于地区数据不在查询集中,所以django需要提出额外的请求来获取这些数据。对于查询集中的每一个房子都是如此,因此,总共是五万次。
当然,解决方案非常简单。为了提取所有需要的序列化数据,你可以在查询集上使用select_related()。因此,get_queryset函数将如下所示:
我们来看看这对性能有何影响:
总体响应时间降至36秒,在数据库中花费的时间约为100ms,只有4个查询!这是个好消息,但我们可以做得更多。
1.2 仅提供相关的数据
默认情况下,Django会从数据库中提取所有字段。但是,当表有很多列很多行的时候,告诉Django提取哪些特定的字段就非常有意义了,这样就不会花时间去获取根本用不到的信息。在本案例中,我们只需要5个字段来进行序列化,虽然表中有17个字段。明确指定从数据库中提取哪些字段是很有意义的,可以进一步缩短响应时间。
Django可以使用defer()和only()这两个查询方法来实现这一点。第一个用于指定哪些字段不要加载,第二个用于指定只加载哪些字段。
这减少了一半的查询时间,非常不错。总体时间也略有下降,但还有更多提升空间。
- 代码优化
你不能无限制地优化数据库查询,并且上面的结果也证明了这一点。即使把查询时间减少到0,我们仍然会面对需要等待半分钟才能得到应答这个现实。现在是时候转移到另一个优化级别上来了,那就是:业务逻辑。
2.1 简化代码
有时,第三方软件包对于简单的任务来说有着太大的开销。本文例程中返回的序列化的房子实例正说明了这一点。
Django REST框架非常棒,包含了很多有用的功能。但是,现在的主要目标是缩短响应时间,所以该框架是优化的候选对象,尤其是我们要使用的序列化对象这个功能非常的简单。
为此,我们来编写一个自定义的序列化器。为了方便起见,我们将用一个静态方法来完成这项工作。
现在看起来好多了,由于没有使用DRF序列化代码,所以响应时间几乎减少了一半。
另外还有一个结果:在请求/响应周期内完成的总的函数调用次数从15,859,427次(上面1.2节的请求次数)减少到了9,257,469次。这意味着大约有三分之一的函数调用都是由Django REST Framework产生的。
2.2 更新或替代第三方软件包
上述几个优化技巧是最常见的,无需深入地分析和思考就可以做到。然而,17秒的响应时间仍然感觉很长。要减少这个时间,需要更深入地了解代码,分析底层发生了什么。换句话说,需要分析一下代码。
你可以自己使用Python内置的分析器来进行分析,也可以使用一些第三方软件包。由于我们已经使用了silk,它可以分析代码并生成一个二进制的分析文件,因此,我们可以做进一步的可视化分析。有好几个可视化软件包可以将二进制文件转换为一些友好的可视化视图。本文将使用snakeviz。
这是上文一个请求的二进制分析文件的可视化图表:
从上到下是调用堆栈,显示了文件名、函数名及其行号,以及该方法花费的时间。可以很容易地看出,时间大部分都用在计算散列上(紫罗兰色的__init__.py和primes.py矩形)。
目前,这是代码的主要性能瓶颈,但同时,这不是我们自己写的代码,而是用的第三方包。
在这种情况下,我们可以做的事情将非常有限:
检查包的最新版本(希望能有更好的性能)。
寻找另一个能够满足我们需求的软件包。
我们自己写代码,并且性能优于目前使用的软件包。
幸运的是,我们找到了一个更新版本的basehash包。原代码使用的是v.2.1.0,而新的是v.3.0.4。
当查看v.3的发行说明时,这一句话看起来令人充满希望:
“使用素数算法进行大规模的优化。”
让我们来看一下!
pip install -U basehash gmpy2
响应时间从17秒缩短到了8秒以内。太棒了!但还有一件事我们应该来看看。
2.3 重构代码
到目前为止,我们已经改进了查询、用自己特定的函数取代了第三方复杂而又泛型的代码、更新了第三方包,但是我们还是保留了原有的代码。但有时,对现有代码进行小规模的重构可能会带来意想不到的结果。但是,为此我们需要再次分析运行结果。
仔细看一下,你可以看到散列仍然是一个问题(毫不奇怪,这是我们对数据做的唯一的事情),虽然我们确实朝这个方向改进了,但这个绿色的矩形表示__init__.py花了2.14秒的时间,同时伴随着灰色的__init__.py:54(hash)。这意味着初始化工作需要很长的时间。
我们来看看basehash包的源代码。
正如你所看到的,一个base实例的初始化需要调用next_prime函数,这是太重了,我们可以在上面的可视化图表中看到左下角的矩形。
我们再来看看Hash类:
正如你所看到的,我已经标记了这两个方法初始化base36实例的方法,这并不是真正需要的。
由于散列是一个确定性的过程,这意味着对于一个给定的输入值,它必须始终生成相同的散列值,因此,我们可以把它作为类的一个属性。让我们来看看它将如何执行:
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!**
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新