在进行Python性能分析后,Django (?) 处理大型数据集时非常慢
我在对比我以前写的一个PHP脚本和一个更新版的Django版本时,发现PHP的那个运行得更快。快得多,甚至让我觉得Django的版本肯定有问题。
先说一下背景:我有一个页面用来显示销售数据的报告。数据可以通过很多条件来过滤,但主要是按日期过滤。这让缓存变得有点困难,因为结果的可能性几乎是无穷无尽的。虽然有很多数字和计算,但在PHP中处理这些从来都不是问题。
更新:
经过一些额外的测试,我发现我的视图中没有导致慢速的原因。如果我只是处理数据并输出5行HTML,速度还算可以(虽然还是比PHP慢),但如果我需要渲染大量数据,那就非常慢。
每当我运行一个大报告(比如说一年的所有销售数据),机器的CPU使用率就会飙到100%。我不知道这是否有什么意义。我使用的是mod_python和Apache。也许换成WSGI会有所帮助?
我的模板标签用来显示小计和总计,处理大数据集时需要0.1秒到1秒不等。我在报告中调用它们大约6次,所以它们似乎不是最大的瓶颈。
现在,我运行了一个Python性能分析工具,得到了这些结果:
Ordered by: internal time List reduced from 3074 to 20 due to restriction ncalls tottime percall cumtime percall filename:lineno(function) 2939417 26.290 0.000 44.857 0.000 /usr/lib/python2.5/tokenize.py:212(generate_tokens) 2822655 17.049 0.000 17.049 0.000 {built-in method match} 1689928 15.418 0.000 23.297 0.000 /usr/lib/python2.5/decimal.py:515(__new__) 12289605 11.464 0.000 11.464 0.000 {isinstance} 882618 9.614 0.000 25.518 0.000 /usr/lib/python2.5/decimal.py:1447(_fix) 17393 8.742 0.001 60.798 0.003 /usr/lib/python2.5/tokenize.py:158(tokenize_loop) 11 7.886 0.717 7.886 0.717 {method 'accept' of '_socket.socket' objects} 365577 7.854 0.000 30.233 0.000 /usr/lib/python2.5/decimal.py:954(__add__) 2922024 7.199 0.000 7.199 0.000 /usr/lib/python2.5/inspect.py:571(tokeneater) 438750 5.868 0.000 31.033 0.000 /usr/lib/python2.5/decimal.py:1064(__mul__) 60799 5.666 0.000 9.377 0.000 /usr/lib/python2.5/site-packages/django/db/models/base.py:241(__init__) 17393 4.734 0.000 4.734 0.000 {method 'query' of '_mysql.connection' objects} 1124348 4.631 0.000 8.469 0.000 /usr/lib/python2.5/site-packages/django/utils/encoding.py:44(force_unicode) 219076 4.139 0.000 156.618 0.001 /usr/lib/python2.5/site-packages/django/template/__init__.py:700(_resolve_lookup) 1074478 3.690 0.000 11.096 0.000 /usr/lib/python2.5/decimal.py:5065(_convert_other) 2973281 3.424 0.000 3.424 0.000 /usr/lib/python2.5/decimal.py:718(__nonzero__) 759014 2.962 0.000 3.371 0.000 /usr/lib/python2.5/decimal.py:4675(__init__) 381756 2.806 0.000 128.447 0.000 /usr/lib/python2.5/site-packages/django/db/models/fields/related.py:231(__get__) 842130 2.764 0.000 3.557 0.000 /usr/lib/python2.5/decimal.py:3339(_dec_from_triple)
tokenize.py的性能最差,这也能理解,因为我在做很多数字格式化。decimal.py的表现也合理,因为报告基本上90%都是数字。我对内置方法match
不太了解,因为我在自己的代码中并没有使用正则表达式(可能是Django在做的?)我用的最接近的就是itertools的ifilter。
看起来这些是主要的问题,如果我能找到减少这些处理时间的方法,那么我的页面就会快很多。
有没有人有什么建议,能让我开始减少这个问题?我真的不知道该如何解决tokenize和decimal的问题,除了直接去掉它们。
更新:我对大部分数据进行了有无过滤的测试,结果时间基本上差不多,后者稍微快一点,但并不足以成为问题的根源。tokenize.py到底发生了什么?
4 个回答
tokenize.py 排名第一,这可能有点道理,因为我在进行很多数字格式化。
这根本没道理。
可以看看 http://docs.python.org/library/tokenize.html。
tokenize 模块为 Python 源代码提供了一个词法分析器,使用 Python 实现。
tokenize 排名第一意味着你的代码正在进行动态解析。
据我所知(在 Django 的代码库中搜索),Django 并没有使用 tokenize。所以这可能意味着你的程序在进行某种动态代码实例化。或者,你只是第一次加载、解析和运行程序时进行性能分析,这样会导致对时间消耗的错误假设。
你绝对不应该在模板标签中进行计算——这很慢。因为这涉及到模板标签的复杂评估。你应该在视图中用简单、开销小的 Python 进行所有计算。模板只用来展示。
另外,如果你不断地进行查询、过滤、求和等操作,那你就需要一个数据仓库。找一本关于数据仓库设计的书,学习数据仓库的设计模式。
你必须有一个中央事实表,周围是维度表。这种设计非常高效。
求和、分组等操作可以在 Python 中使用 defaultdict
来完成。一次性获取所有行,构建包含所需结果的字典。如果这样做太慢,那么你需要使用数据仓库技术,将持久的求和和分组与详细的事实数据分开。通常这需要跳出 Django 的 ORM,使用关系数据库管理系统(RDBMS)的特性,比如视图或派生数据表。
当你处理大量数据时,使用 ValuesQuerySet 可以节省很多CPU和内存。因为它直接访问查询结果,而不是为结果中的每一行都创建一个模型对象。
它的用法大概是这样的:
Blog.objects.order_by('id').values()
在没有任何代码示例的情况下,我们需要对你的问题做很多假设。
我的假设是:你在使用Django自带的ORM工具和模型(比如说 sales-data = modelobj.objects().all()),而在PHP那边你是在直接写SQL查询,并且在处理一个查询集。
Django在把数据库查询结果转换成ORM/模型对象时,会进行很多类型转换和数据类型的处理,而这些处理都是由默认的管理器(objects())来完成的。
而在PHP中,你可以自己控制这些转换,清楚地知道如何把一种数据类型转换成另一种,这样就能节省一些执行时间。
我建议你尝试把一些复杂的数字计算放到数据库里去处理,特别是当你在处理记录集的时候——数据库特别擅长这种处理。在Django中,你可以直接发送原始SQL到数据库:http://docs.djangoproject.com/en/dev/topics/db/sql/#topics-db-sql
希望这些建议能帮你找到正确的方向……