在进行Python性能分析后,Django (?) 处理大型数据集时非常慢

6 投票
4 回答
7520 浏览
提问于 2025-04-15 13:07

我在对比我以前写的一个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 个回答

2

tokenize.py 排名第一,这可能有点道理,因为我在进行很多数字格式化。

这根本没道理。

可以看看 http://docs.python.org/library/tokenize.html

tokenize 模块为 Python 源代码提供了一个词法分析器,使用 Python 实现。

tokenize 排名第一意味着你的代码正在进行动态解析。

据我所知(在 Django 的代码库中搜索),Django 并没有使用 tokenize。所以这可能意味着你的程序在进行某种动态代码实例化。或者,你只是第一次加载、解析和运行程序时进行性能分析,这样会导致对时间消耗的错误假设。

绝对不应该在模板标签中进行计算——这很慢。因为这涉及到模板标签的复杂评估。你应该在视图中用简单、开销小的 Python 进行所有计算。模板只用来展示。

另外,如果你不断地进行查询、过滤、求和等操作,那你就需要一个数据仓库。找一本关于数据仓库设计的书,学习数据仓库的设计模式。

你必须有一个中央事实表,周围是维度表。这种设计非常高效。

求和、分组等操作可以在 Python 中使用 defaultdict 来完成。一次性获取所有行,构建包含所需结果的字典。如果这样做太慢,那么你需要使用数据仓库技术,将持久的求和和分组与详细的事实数据分开。通常这需要跳出 Django 的 ORM,使用关系数据库管理系统(RDBMS)的特性,比如视图或派生数据表。

2

当你处理大量数据时,使用 ValuesQuerySet 可以节省很多CPU和内存。因为它直接访问查询结果,而不是为结果中的每一行都创建一个模型对象。

它的用法大概是这样的:

Blog.objects.order_by('id').values()
7

在没有任何代码示例的情况下,我们需要对你的问题做很多假设。

我的假设是:你在使用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

希望这些建议能帮你找到正确的方向……

撰写回答