読者です 読者をやめる 読者になる 読者になる

djangoとgenshiの処理速度の比較

Python

 少し前の話題になりますが、id:Voluntasさんが何回かにわたって、djangoとgenshiの処理時間の比較を行われています。個人的にもGenshiとDjangoのテンプレート・エンジンの性能差には関心があるので、僕も実際に行ってみました。
 ただし、比較を行う際には、Genshiが、テンプレート解析時に、ディレクティブや式をバイトコードに変換する作業を行っているということを考慮に入れておかなくてはなりません。この処理時間までベンチマークに含めてしまうと、GenshiとDjangoでは桁が違う数字が返ってきます。
 Webアプリケーションで使用するという前提でベンチマークを計測するならば、レンダリングにかかる時間を比較した方が良いでしょうから、元のコードを次のように変えました。

import os
os.environ["DJANGO_SETTINGS_MODULE"] = "__main__"

from django.template import Template, Context 

def django_template():
    tmpl = Template('''Dear {{ name }},
    We have the following items for you:
    {% for item in items %}
    * {{ item }}
    {% endfor %}
    All the best,
    Foobar''')
    
    def render():
        return tmpl.render(Context({'name': 'Joe', 'items': [1, 2, 3]}))
    
    return render

 Genshiのテストコードの方も、これと同じようにネストした関数を使って、テンプレートの解析を一回だけおこなうようにします。
 その上で、timeitを使って、1000回試行時の処理時間を比較しました。

Genshi 0.409703969955
Django 0.253922939301


 Genshiのサイトに掲載されているベンチマーク結果に近い結果が得られます。
 これだけでは芸がないので、hotshotを使って両者の比較を行ってみます。ここでは、Genshiのプロファイル結果の上位のみを掲載しておきます。

         110001 function calls (100001 primitive calls) in 0.643 CPU seconds

   Ordered by: internal time, call count

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    14000    0.111    0.000    0.501    0.000 output.py:416(__call__)
    14000    0.110    0.000    0.329    0.000 template.py:852(_eval)
    14000    0.059    0.000    0.390    0.000 core.py:200(_ensure)
     1000    0.059    0.000    0.592    0.001 core.py:128(render)
24000/14000    0.058    0.000    0.143    0.000 template.py:908(_flatten)
     5000    0.045    0.000    0.094    0.000 eval.py:83(evaluate)

... 以下略

 インタプリタモードで、DjangoとGenshiのテンプレート解析後の構文木をダンプしてみると分かるのですが、両者の構文木の構造やノード数は、このケースでは全く同じです。
 実装に多少の差があったとしても、100%近い処理時間の差が出るということは、Genshiのテンプレートが、Djangoテンプレートでは行われていない処理を行っているからと予想できます。
 hotshotの結果に見られる、"core.py:200(_ensure)", "template.py:908(_flatten)"というのが、その「Djangoでは行われていない処理」で、簡単に言うと、前者がXMLエンティティ(例えば¥)をユニコード・コードボイントに変換する処理、後者が他のテンプレートをインクルードする処理になります。
 どちらも、今回の事例では関係のない処理なのですが、Genshiの現バージョンでは各ノードを訪問するたびに「処理を行うか否か」をIf-Else条件式で評価するような実装になっているために、処理時間が余分にかかっています。このあたりは、Genshiの今後の課題なのかと思います。
 しかし、DjangoのGenshiの成熟度を度外視したとしても、Djangoテンプレートが効率性重視の設計・実装であるという傾向は否定できません。
 それは、1000回試行した際に、hotshot結果として表示される総関数呼び出し数が、Djangoは52001に対して、Genshiは110001と、2倍近い数字に上っていることからもうかがえます。


 今回、比較に使用したコードは、id:Voluntasさんが公開されているコードとGenshiの配布物に含まれるコードを切り貼りして作ったもので、立派なものではないのですが、追試を行いたい方もいるかもしれませんので、一応公開しておきます。(runprofile.py.txt)
 次のように使います。

$ python runprofile.py genshi # hotshotプロファイリングを実施
$ python runprofile.py django
$ python runprofile.py genshi timeit # timeitテストを実施
$ python runprofile.py django timeit