django ipython shell_希望能早点了解的Django性能优化技巧

本文介绍了在Django项目中提升代码性能的方法,包括使用line_profiler分析瓶颈,开启SQL记录,避免循环查询,利用select_related和prefetch_related减少查询,以及使用values()和values_list()优化序列化。通过实例演示了这些技术如何显著提高查询效率。

编者注:原文发表于Medium, 作者Ryley Sill, 大江狗翻译整理,原文链接在本文结尾。本文值得收藏天天刷,可以帮你避免写出糟糕的代码。

我最近通过Django建立了Carta的场景建模平台。通过一些研究,我意识到我并不知道如何编写高性能的代码。我现在仍然不是很懂,但是我希望自己能早点了解这些知识。

免责声明

以下旨在向您介绍可用于优化Django代码的不同技术。结果将根据您的数据库,模型和数据的性质而有所不同。

入门-示例数据模型

我创建了一个由书籍,页面,作者和图书馆组成的简单数据模型,因此我们可以使用下面模型概述本文的技术。该数据库拥有10,000本书,1,000,000页,1,000位作者和1,000个图书馆。这些页面随机分配给书籍,而这些图书则随机分配给作者和图书馆。

class Library(models.Model):    name = models.CharField(max_length=200, default='')    address = models.CharField(max_length=200, default='')class Author(models.Model):    name = models.CharField(max_length=200, default='')class Book(models.Model):    library = models.ForeignKey(        Library,        on_delete=models.CASCADE,        related_name='books',    )    author = models.ForeignKey(        Author,        on_delete=models.CASCADE,        related_name='books'    )    title = models.CharField(max_length=200, default='')    address = models.CharField(max_length=200, default='')            def get_page_count(self):                return self.pages.count()class Page(models.Model):    book = models.ForeignKey(        Book,        on_delete=models.CASCADE,        related_name='pages',    )    text = models.TextField(null=True, blank=True)    page_number = models.IntegerField()

使用line_profiler

如果我们不知道为什么我们的代码很慢,就很难弄清楚如何对其进行优化。line_profiler是一个很酷的python模块,它告诉我们执行一个函数中的每一行需要多少时间。在开始之前,请使用安装软件包pip install line_profiler

这是我通常使用的方式。想象一下,我们需要一个函数来返回数据库中与每个图书馆(library)相对应的所有书籍列表。假设我们最终接到了这样的任务,我们可能按如下写代码,返回结果是一个字典,key是每个图书馆的id,value是书籍列表……

def get_books_by_library_id():    libraries = Library.objects.all()    result = {}    for library in libraries:        books_in_library = library.books.all()        result[library.id] = list(books_in_library)    return result

当数据库中数据很少时,上述查询会运行得很快,然而在我们的例子中,它将运行的非常,因为数据众多。要找出函数执行过程中计算机在哪里花费了最多的时间,可以IPython在调用可疑函数的位置之前在视图函数中添加调试器。

from django.http import HttpResponsefrom test_app.helpers import get_books_by_library_iddef books_by_library_id_view(request):    from IPython import embed; embed()    books_by_library_id = get_books_by_library_id()    ...    return HttpResponse(response)

…执行任何触发视图的操作,等待服务器停止运行并IPython启动shell程序,line_profiler将其作为扩展加载到IPython外壳程序中,然后使用来配置功能lprun -f your_function_name your_function_name()。如果需要将任何参数传递到函数中,请将其传递到该命令最后一部分的括号内。这时你将看到每行程序的运行时间。

2538f2cca4052f948e869d7296e9de45.png

该表% Time列会告诉拟执行该代码行花了多少时间。因此,在47.977秒的执行时间中,执行整个函数最耗时的是第6行,占总时间的98.8%,在这里我们library.books.all()通过将强制获取每个library里的数据。

该表Hits列告诉您该行代码执行了多少次数据库查询。看起来第6行执行了1000次Library数据库的查询。这意味着我们要进行1000个SQL查询,这可能就是为什么此功能如此缓慢的原因。在后面的部分中,我们将介绍如何使其更快。

开启SQL记录

当我们真正地研究一个函数来查看瓶颈在哪里时,打开SQL日志记录可能会很有用。

# settings.pyLOGGING = {    'version': 1,    'filters': {        'require_debug_true': {            '()': 'django.utils.log.RequireDebugTrue',        }     },     'handlers': {        'console': {            'level': 'DEBUG',            'filters': ['require_debug_true'],            'class': 'logging.StreamHandler',         }    },    'loggers': {        'django.db.backends': {            'level': 'DEBUG',            'handlers': ['console'],        }    }}

我们可以添加此代码段以settings.py获取代码中执行的每个SQL查询的完整打印输出。通常在任何时候都保持太多时间,但是在给定的代码块中查看正在执行多少个查询以及执行每个查询需要花费多长时间可能会很有启发。

我通常使用SQL日志记录的方式是将调试器放在可疑的代码块之前和之后。然后,我执行代码,单击第一个调试器后清除终端,继续执行第一个调试器,并调查在两次调试之间执行的查询,同时注意重复,意外或缓慢的查询。

这篇文章不会涉及SQL查询优化,但是我们将讨论如何减少或消除函数中重复查询的数量。

大江狗注:使用django debug toolbar查看SQL查询次数与耗时比Ipython和SQL日志更方便。避免循环查询

def get_books_by_library_id():    libraries = Library.objects.all()    result = {}    for library in libraries:        books_in_library = library.books.all()        result[library.id] = list(books_in_library)    return result

回到我们那个身患绝症的功能get_books_by_library,问题在于我们需要频繁地访问查询每个图书馆,以获取每个图书馆的书籍列表。如果您只有几个图书馆,每个图书馆有数百万本书,这代码可能就是您想要的。但是如果您有成千上万的图书馆,每个图书馆都有成千上万的书,那么您的代码现在将执行成千上万次的查询,才能完成这个简单的函数。

为了解决这个问题,我们可以预先获取所有书籍,如下所示:

def get_books_by_library_id_one_query():    books = Book.objects.all()    result = defaultdict(list)    for book in books:        result[book.library_id].append(book)    return result

BOOM. 现在无论数据库中存在多少个图书馆,您都只执行一次SQL查询, 即可获取每个图书馆的id,及每个图书馆id对应的书籍列表了,返回结果和前面是一样的。

那么我们如何知道这是否更快呢?Python有一个很酷的小模块,称为timeit,告诉您执行一个函数需要多长时间。

In [12]: timeit(get_books_by_library_id, number=10)Out[12]: 6.598360636999132In [13]: timeit(get_books_by_library_id_one_query, number=10)Out[13]: 0.677092163998168

尽管我们的第二个函数必须循环处理10,000本书,但是通过消除频繁访问数据库的次数,它的运行速度仍比原始功能快近10倍。请记住,这一切都取决于数据的稀疏性/密度和规模。以我的经验,如果SQL查询的数量随着其他一些输入的增加而增加,则代码运行通常会变慢。

select_related()

想象一下,如果你希望以Harry Potter and the Sorcerer's Stone by J.K. Rowling字符串的形式导出一个图书馆里所有的书籍数据。

Django有些方法使减少不必要的数据库查询变得容易。如果您不了解幕后发生的事情或者不了解这些方法,你可能会写出如下代码:

def get_books_by_author():    books = Book.objects.all()    result = defaultdict(list)    for book in books:        author = book.author        title_and_author = '{} by {}'.format(            book.title,            author.name        )        result[book.library_id].append(title_and_author)    return result

问题出在这里:每次访问时,book.author您所做的查询都等同于Author.objects.get(id=book.author_id)。如果要遍历成千上万本书籍,那么您还要进行成千上万个完全不必要的查询。使用select_related可以避免这一点:

def get_books_by_author_select_related():    books = Book.objects.all().select_related('author')    result = defaultdict(list)    for book in books:        author = book.author        title_and_author = '{} by {}'.format(            book.title,            author.name        )        result[book.library_id].append(title_and_author)    return result

select_related通过执行一个更复杂的SQL查询来工作,该查询还返回相关对象的字段。因此,您不仅要获取有关所有书籍的数据,而且还要获取每本书作者的数据。现在,当您访问时,您book.author实际上是在访问作者的缓存版本,而不是进行单独的数据库查询。

使用select_related后到底快多少?我使用该timeit模块运行了这两个函数,发现该函数使用select_related速度提高了32倍:

In [12]: timeit(get_books_by_author, number=10)Out[12]: 41.363460485998075In [13]: timeit(get_books_by_author_select_related, number=10)Out[13]: 1.2787263889913447

注:为了避免跨“多对多”关系加入会产生更大的结果集,select_related仅限于单对多和一对一关系。为了遍历反向ForeignKey或ManyToMany关系,我们需要prefetch_related方法。

prefetch_related()

prefetch_related类似于select_related防止不必要的SQL查询。不像select_related一次性获取主要和相关对象,prefetch_related对每种关系进行单独的查询,然后将结果“结合”在一起。这种方法的缺点是它需要多次往返数据库。

Author.objects.filter(name__startswith ='R').prefetch('books')

工作原理:首先触发一个请求,该请求运行主查询Author.objects.filter(name__startswith=letter),然后Book.objects.filter(author_id__in=PKS_OF_AUTHORS_FROM_FIRST_REQUEST)执行,最后将两个响应合并到一个查询集中,该查询集中将Author每个作者的书缓存在内存中。因此,您最终得到的结果与相似,select_related但您通过不同的方式到达那里。

尽管您可以prefetch_related在任何使用的地方使用select_related,但是通常,您的代码可以更快地运行,select_related因为它可以在一个SQL查询中获取所需的一切。但是,如果您的数据特别稀疏(几百万本书到几个图书馆),尽管有额外的数据库旅行,您可能会看到性能提高。因此,如果有疑问,请尝试两种方法,然后看看哪种方法最重要。

总结一下:如果你进行数据库查询时还需要获取关联对象的信息,使用select_related以及prefetch_related

values()和values_list()

将SQL响应序列化为python数据所花费的时间与返回的行数和列数成正比。在下面的函数中,即使我们只需要作者名字,书的图书馆id和书籍名称 ,我们也将书和作者模型上所有字段进行序列化了。我们还无缘无故地初始化Django模型实例,尽管我们没有对其进行任何特殊处理(例如调用模型方法)。

def get_books_by_author_select_related():    books = Book.objects.all().select_related('author')    result = defaultdict(list)    for book in books:        author = book.author        title_and_author = '{} by {}'.format(            book.title,            author.name        )        result[book.library_id].append(title_and_author)    return result

因此,我们产生了相当大的开销,可以通过调用或在queryset上仅询问我们需要的字段来消除这些开销:.values().values_list()

def get_books_by_author_select_related_values():    books = (        Book.objects         .all()         .select_related('author')         .values('title', 'library_id', 'author__name')    )    result = defaultdict(list)    for book in books.iterator():        title_and_author = '{} by {}'.format(            book['title'],            book['author__name']        )        result[book['library_id']].append(title_and_author)    return resultdef get_books_by_author_select_related_values_list():    books = (        Book.objects         .all()         .select_related('author')         .values_list('title', 'library_id', 'author__name')    )    result = defaultdict(list)    for book in books.iterator():        title_and_author = '{} by {}'.format(            book[0],            book[2]        )        result[book[1]].append(title_and_author)    return result

.values()返回模型实例的字典表示形式的列表:[{'title': 'Snow Crash', 'library_id': 9, 'author__name': 'Neil'}, ...].values_list()返回表示模型实例的元组的列表[('Snow Crash', 9, 'Neil'), ...]

那么这些功能要快多少?通过仅抓住我们需要的字段,这些函数使用.values().values_list()运行的速度比原始函数快7倍。

In [13]: timeit(get_books_by_author_select_related, number=10)Out[13]: 1.2787263889913447In [14]: timeit(get_books_by_author_select_related_values, number=1)Out[14]: 0.19064296898432076In [15]: timeit(get_books_by_author_select_related_values_list, number=1)Out[15]: 0.17425400999491103

这里要注意的是这些模型是超轻量级的-模型上只有4个字段Book。如果我们使用具有大量字段的模型,则结果将更加极端。即使您需要对象上的所有字段,.values().values_list()可以通过跳过模型初始化来显着提高性能。您可以通过不传递任何字段作为参数来获取所有字段。

# returns list of model instancesdef get_book_instances():    return list(        Book.objects        .all()        .select_related('author')    )# returns list of dictionaries representing model instancesdef get_book_dictionaries():    return list(        Book.objects        .all()        .select_related('author')        .values()    )# returns a list of dictionaries with the name of each bookdef get_book_dictionaries_title_only():    return list(        Book.objects        .all()        .select_related('author')        .values('title')    )

获得书籍的字典表示速度比请求模型实例快6.5倍,并且要求书籍中的特定字段的速度快8.8倍。

In [64]: timeit(get_book_instances, number=100)Out[64]: 12.904168864974054In [65]: timeit(get_book_dictionaries, number=100)Out[65]: 2.049193776998436In [66]: timeit(get_book_dictionaries_title_only, number=100)Out[66]: 1.4734381759772077

初始化Django模型实例非常昂贵。如果仅使用模型上的数据,则最好使用其字典或元组表示形式。

bulk_create()

这很简单。如果我们要一次创建多个对象,请使用bulk_create而不是在循环中创建对象。顾名思义,bulk_create无论我们要插入多少个对象,都将使用一个查询将对象列表插入数据库。

可能插入的对象如此之多,以至于生成的单个查询bulk_create比多个较小的查询要慢。在这种情况下,您可以batch_size=SIZE_OF_BATCH作为参数传入,这会将主查询分解为较小的查询。

我不知道有关在合理使用之前需要插入的对象数量batch_size或如何确定批处理大小的经验法则。通常,我会忽略它,直到明确存在瓶颈,然后batch_size从那里确定一个合适的瓶颈。

SQL(通常)比Python快

假设您想要一个返回每个图书馆所有书籍的页面数之和。使用上面学到的知识,您可能会得到如下代码:

def get_page_count_by_library_id():    result = defaultdict(int)    books = Book.objects.all().prefetch_related('pages')    for book in books:        result[book.library_id] += book.get_page_count()    return result

即使这只会触发2次查询,我们仍然必须将所有书籍拉入内存并遍历其中的每一本书。这种事情很容易通过annotation解决。

from django.db.models import Sumdef get_page_count_by_library_id_using_annotation():    result = {}    libraries = (        Library.objects        .all()        .annotate(page_count=Sum('books__pages'))        .values_list('id', 'page_count')    )    for library_id, page_count in libraries:        result[library_id] = page_count    return result

现在,您无需拉出一堆Django实例到内存中并在每个实例上调用模型方法,而只是拉出您实际上关心的两个值Libraryid和和page_count。我们的新功能运行速度比原始功能快115倍。

In [66]: timeit(get_page_count_by_library_id, number=10)Out[66]: 158.0743614450039In [67]: timeit(get_page_count_by_library_id_using_annotation, number=10)Out[67]: 1.3725216790044215

这个例子很简单,但是annotation可以做很多事情。如果在大型查询集上进行数学运算时遇到性能问题,请考虑编写annotation以将其工作交给数据库。

总结

如果您正在从事Django的项目开发,而对性能几乎没有考虑或没有考虑过,那么本文低调的成果可以帮助您入门。在大多数情况下,上述技术将大大提高性能,而不会在代码的可读性上打折。

原文:

https://medium.com/@ryleysill93/basic-performance-optimization-in-django-ebd19089a33f

相关阅读

Django QuerySet查询基础与技巧。有了她,再也不用担心SQL注入了。

Django基础(29): select_related和prefetch_related的用法与区别

5f04fbdbabef2d4bf6aaf9757ace88ae.png

多源动态最优潮流的分布鲁棒优化方法(IEEE118节点)(Matlab代码实现)内容概要:本文介绍了基于Matlab实现的多源动态最优潮流的分布鲁棒优化方法,适用于IEEE118节点电力系统。该方法旨在应对电力系统中源荷不确定性带来的挑战,通过构建分布鲁棒优化模型,有效处理多源输入下的动态最优潮流问题,提升系统运行的安全性和经济性。文中详细阐述了模型的数学 formulation、求解算法及仿真验证过程,并提供了完整的Matlab代码实现,便于读者复现与应用。该研究属于电力系统优化调度领域的高水平技术复现,具有较强的工程实用价值。; 适合人群:具备电力系统基础知识和Matlab编程能力的研究生、科研人员及从事电力系统优化调度的工程技术人员,尤其适合致力于智能电网、鲁棒优化、能源调度等领域研究的专业人士。; 使用场景及目标:①用于电力系统多源环境下动态最优潮流的建模与求解;②支撑含可再生能源接入的电网调度决策;③作为鲁棒优化方法在实际电力系统中应用的教学与科研案例;④为IEEE118节点系统的仿真研究提供可复现的技术支持。; 阅读建议:建议结合提供的Matlab代码逐模块分析,重点关注不确定变量的分布鲁棒建模、目标函数构造及求解器调用方式。读者应具备一定的凸优化和电力系统分析基础,推荐配合YALMIP工具包与主流求解器(如CPLEX、Gurobi)进行调试与扩展实验。
内容概要:本文系统介绍了物联网与云计算的基本概念、发展历程、技术架构、应用场景及产业生态。文章阐述了物联网作为未来互联网的重要组成部分,通过RFID、传感器网络、M2M通信等技术实现物理世界与虚拟世界的深度融合,并展示了其在智能交通、医疗保健、能源管理、环境监测等多个领域的实际应用案例。同时,文章强调云计算作为物联网的支撑平台,能够有效应对海量数据处理、资源弹性调度和绿色节能等挑战,推动物联网规模化发展。文中还详细分析了物联网的体系结构、标准化进展(如IEEE 1888、ITU-T、ISO/IEC等)、关键技术(中间件、QoS、路由协议)以及中国运营商在M2M业务中的实践。; 适合人群:从事物联网、云计算、通信网络及相关信息技术领域的研究人员、工程师、高校师生以及政策制定者。; 使用场景及目标:①了解物联网与云计算的技术融合路径及其在各行业的落地模式;②掌握物联网体系结构、标准协议与关键技术实现;③为智慧城市、工业互联网、智能物流等应用提供技术参考与方案设计依据;④指导企业和政府在物联网战略布局中的技术选型与生态构建。; 阅读建议:本文内容详实、覆盖面广,建议结合具体应用场景深入研读,关注技术标准与产业协同发展趋势,同时结合云计算平台实践,理解其对物联网数据处理与服务能力的支撑作用。
标题基于Java的停车场管理系统设计与实现研究AI更换标题第1章引言介绍停车场管理系统研究背景、意义,分析国内外现状,阐述论文方法与创新点。1.1研究背景与意义分析传统停车场管理问题,说明基于Java系统开发的重要性。1.2国内外研究现状综述国内外停车场管理系统的发展现状及技术特点。1.3研究方法以及创新点介绍本文采用的研究方法以及系统开发中的创新点。第2章相关理论总结Java技术及停车场管理相关理论,为系统开发奠定基础。2.1Java编程语言特性阐述Java的面向对象、跨平台等特性及其在系统开发中的应用。2.2数据库管理理论介绍数据库设计原则、SQL语言及在系统中的数据存储与管理。2.3软件工程理论说明软件开发生命周期、设计模式在系统开发中的运用。第3章基于Java的停车场管理系统设计详细介绍系统的整体架构、功能模块及数据库设计方案。3.1系统架构设计阐述系统的层次结构、模块划分及模块间交互方式。3.2功能模块设计介绍车辆进出管理、车位管理、计费管理等核心功能模块设计。3.3数据库设计给出数据库表结构、字段设计及数据关系图。第4章系统实现与测试系统实现过程,包括开发环境、关键代码及测试方法。4.1开发环境与工具介绍系统开发所使用的Java开发环境、数据库管理系统等工具。4.2关键代码实现展示系统核心功能的部分关键代码及实现逻辑。4.3系统测试方法与结果阐述系统测试方法,包括单元测试、集成测试等,并展示测试结果。第5章研究结果与分析呈现系统运行效果,分析系统性能、稳定性及用户满意度。5.1系统运行效果展示通过截图或视频展示系统实际操作流程及界面效果。5.2系统性能分析从响应时间、吞吐量等指标分析系统性能。5.3用户满意度调查通过问卷调查等方式收集用户反馈,分析用户满意度。第6章结论与展望总结研究成果,提出系统改进方向及未来发展趋势。6.1研究结论概括基于Java的停车场管理
根据原作 https://pan.quark.cn/s/a4b39357ea24 的源码改编 QT作为一个功能强大的跨平台应用程序开发框架,为开发者提供了便利,使其能够借助C++语言编写一次代码,便可在多个操作系统上运行,例如Windows、Linux、macOS等。 QT5.12是QT框架中的一个特定版本,该版本引入了诸多改进与新增特性,包括性能的提升、API支持的扩展以及对现代C++标准的兼容性。 在QT5.12环境下实现后台对鼠标侧键的监控,主要涉及以下几个关键知识点:1. **信号与槽(Signals & Slots)机制**:这一机制是QT的核心,主要用于实现对象之间的通信。 在监测鼠标事件时,可以通过定义信号和槽函数来处理鼠标的点击行为,比如,当鼠标侧键被触发时,会触发一个信号,然后将其连接至相应的槽函数以执行处理。 2. **QEvent类**:在QT中,QEvent类代表了多种类型的事件,涵盖了键盘事件、鼠标事件等。 在处理鼠标侧键时,需要关注`QEvent::MouseButtonPress`和`QEvent::MouseButtonRelease`事件,尤其是针对鼠标侧键的独特标识。 3. **QMouseEvent类**:每当鼠标事件发生,系统会发送一个QMouseEvent对象。 通过这个对象,可以获取到鼠标的按钮状态、位置、点击类型等信息。 在处理侧键时,可以检查`QMouseEvent::button()`返回的枚举值,例如`Qt::MiddleButton`表示的是鼠标中键(即侧键)。 4. **安装事件过滤器(Event Filter)**:为了在后台持续监控鼠标,可能需要为特定的窗口或对象安装事件过滤器。 通过实现`QObject::eventFilter...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值