パフォーマンスと最適化Link to this heading
このドキュメントでは、Django コードをより効率よく、早く、より少ないリソースで実行するためのテクニックとツールについて、その概要を説明します。
はじめにLink to this heading
一般に、最も関心があるのは、コードが正しく 動作する、つまり、書かれたコードのロジックがが期待通りの出力を生成することです。しかし、それだけでは、期待通りに 効率よく 動作しているとは言えません。
この場合、必要なのは、コードの振る舞いに影響を与えることなく、あるいは影響を最小限に保ちつつ、そのパフォーマンスを向上させる実用的な方法です。
一般的なアプローチLink to this heading
何のために 最適化をしようとしているのか?Link to this heading
最適化する「パフォーマンス」という言葉で何を意味しているのか、はっきりと考えておくことが大切です。パフォーマンスを測る指標は1つではないからです。
高速化というものがプログラムのために最も明らかな目的かもしれません。しかし、他の観点からパフォーマンスを向上することもできます。たとえば、メモリー消費量を少なくするとか、データベースやネットワークのアクセス量を削減するといったことが考えられます。
1つの領域を改善すると、別の領域の性能も向上することがよくありますが、常にそうとは限らず、一方が他方を犠牲にすることさえあります。たとえば、プログラムの速度の改善がメモリ使用量の増加を引き起こすかもしれません。さらに悪いことに、それが自滅的になるかもしれません。もし速度の改善がメモリを大幅に消費してしまったら、システム上のメモリが足りなくなり、結果的に利益よりも害を与えることになってしまうでしょう。
他にも気に留めておく必要があるトレードオフがあります。あなた自身の時間が CPU 時間以上に貴重なリソースだということです。一部の改善は実装する価値がないほど難しすぎるかもしれず、ポータビリティやコードメンテナンスに影響を与えてしまうかもしれません。すべての性能改善に努力する価値があるわけではないのです。
そのため、何を目的としたパフォーマンスの改善なのかを知っておくべき必要があります。また、その目的とした方針に相応の理由があるかを知っておく必要もあります。
パフォーマンスのベンチマークLink to this heading
コード中で効率の悪い部分を、単に想像したり当て推量したりするというのは、あまり良い考えではありません。
Django のツールLink to this heading
django-debug-toolbar は非常に便利なツールで、コードが何をしているか、どのくらいの時間を費やしているかを把握するのに役立ちます。特に、ページが生成しているすべてのSQLクエリと、それぞれのクエリにかかった時間を表示することができます。
サードパーティ製のパネルをツールバーに追加することも可能です。それにより、たとえば、キャッシュのパフォーマンスや、テンプレートのレンダリング時間を測定できます。
サードパーティのサービスLink to this heading
サイト上のページのパフォーマンスを分析・レポートしてくれる無料のサービスが数多く存在します。これらのサービスでは、実際のユーザーエクスペリエンスをリモートの HTTP クライアントの観点からシミュレートしてくれます。
これらのサービスは、コードの内部についてレポートすることはできませんが、Django 環境の内部からは十分に測定できない観点も含めて、サイト全体のパフォーマンスについて役に立つ見解を提供してくれます。
同様の分析を実施する有料サービスもいくつかあり、一部は Django を認識するため、コードベースと統合して性能のプロファイルをより包括的に得られます。
最初から物事を正しく行うLink to this heading
最適化には、パフォーマンスの悪い部分を改善する作業も含まれますが、パフォーマンスの向上を考える前に取り入れるべき点として、どんな場合でも作業に採用できる良い習慣があります。
この点においてPython は優れた言語であり、エレガントに見え、正しいと感じる解決策が通常、最も性能が良いものです。ほとんどのスキルと同様に、「正しいと見える」ものを学ぶには実践が必要ですが、最も有用なガイドラインの一つはこれです:
適切なレベルで作業するLink to this heading
Django は様々なアプローチ方法を提供しますが、ある方法で何かができるからといって、それが最も適切な方法であるとは限りません。例えば、同じこと (コレクション内のアイテムの数など) を QuerySet で、 Python で、あるいはテンプレートで計算できるかもしれません。
しかし、この作業を高いレベルよりも低いレベルで行う方が、ほとんどの場合、速くなります。高いレベルでは、システムは複数の抽象化レベルとマシンの層を通じてオブジェクトを扱わなければなりません。
つまり、データベースは通常、Pythonよりも速く処理ができ、Pythonはテンプレート言語よりも速く処理ができます:
# QuerySet operation on the database
# fast, because that's what databases are good at
my_bicycles.count()
# counting Python objects
# slower, because it requires a database query anyway, and processing
# of the Python objects
len(my_bicycles)
<!--
Django template filter
slower still, because it will have to count them in Python anyway,
and because of template language overheads
-->
{{ my_bicycles|length }}
通常、その仕事に最も適したレベルは、コードを書くのに快適な最も低いレベルのものです。
キャッシュLink to this heading
値の計算は高コストになる(つまり、リソースを多く消費し、遅い)ことが多いので、その値をすぐにアクセス可能なキャッシュに保存しておくことで、次に必要になる時のための大きなメリットになります。
これは十分に重要で強力な技術なため、Djangoには包括的なキャッシュフレームワークと他の小さなキャッシュ機能の部品が含まれています。
キャッシュフレームワークLink to this heading
Django の キャッシュフレームワーク は、動的なコンテンツを保存して、リクエストごとに再計算しないで済むようにすることで、パフォーマンスを向上するための非常に大きな可能性を提供します。
利便性のため、Django は様々なレベルのキャッシュの粒度を提供しています。特定のビューの出力をキャッシュしたり、生成するのに時間のかかるパーツだけをキャッシュしたり、あるいは、サイト全体をキャッシュすることでさえ可能です。
キャッシュの実装は、書き方が良くないことが原因でパフォーマンスが悪いコードを改善する手段として考えるべきではありません。キャッシュは、パフォーマンスの良いコードを生み出すための最終段階の一つであり、近道ではありません。
cached_propertyLink to this heading
クラスのインスタンスのメソッドを複数回呼ぶ必要があるというのはよくあることです。その関数の実行が高コストである場合、複数回の呼び出しは無駄が多いです。
cached_property デコレータを使うことで、プロパティが返す値を保存して、次回同じインスタンスで関数が呼ばれた時、その値を再計算する代わりに、保存しておいた値を返すようにすることができます。ただし、このデコレータが機能するのは、メソッドが引数として self のみを取り、メソッドをプロパティーに変えられる場合のみなので、注意してください。
特定の Django コンポーネントは、独自のキャッシュ機能を実装しています。以下のそれらのコンポーネントに関係するセクションで解説します。
遅延について理解するLink to this heading
遅延 (laziness) とは、キャッシュ機能を補完するもう1つの戦略です。キャッシュは結果を保存することで再計算を防ぎますが、遅延は実際に値が必要になるまで、計算の実行を遅らせます。
遅延によりインスタンス化される前に、またはインスタンス化が可能になる以前であっても参照できるようになります。これにはさまざまな用途があります。
たとえば、遅延翻訳 は対象となる言語自体が判別される前に使用できます。翻訳後の文字列がレンダリングされたテンプレートなどで実際に必要になるまで使用されなくなるためです。
そもそも怠惰(Laziness)とは、仕事を避けることで労力を節約することでもあります。つまり、怠け癖の一面は、やらなければならないことがあるまで何もしないことです。そのため、怠け癖はパフォーマンスにも影響し、高価な仕事であればあるほど、怠け癖によって得られるものは大きくなります。
Python provides a number of tools for lazy evaluation, particularly through the generator and generator expression constructs. It's worth reading up on laziness in Python to discover opportunities for making use of lazy patterns in your code.
Django における遅延Link to this heading
Django は遅延をうまく使います。このよい例として、QuerySet の評価が挙げられます。QuerySet は遅延評価されます。そのため、QuerySet は作成後、記述したアイテムを取得するためにデータベースへのアクセスを一切行うことなく、さまざまな場所に渡したり、他の QuerySet インスタンスと組み合わせることができます。渡されるのは QuerySet オブジェクトであって、最終的にはデータベースから取得されるアイテムのコレクションではないのです。
他方で、特定の操作は QuerySet の評価を強制します。QuerySet の時期尚早な評価を回避することで、高コストで不必要なデータベースへのアクセスを節約できます。
Django には keep_lazy() デコレータもあります。これにより、遅延引数で呼び出された関数は、必要なときだけ評価されるようになります。このため、遅延引数 (高コストな引数である可能性もあります) は、必要なときだけ評価されるようになります。
データベースLink to this heading
データベースの最適化Link to this heading
Djangoのデータベース層は、開発者がデータベースを最大限活用できるように、さまざまな方法を提供しています。 データベースアクセスの最適化ドキュメント では、データベースの利用を最適化する際にとるべき手順を、大まかにいくつかの見出しとしてまとめています。それぞれの見出しの下に、関連するドキュメントへのリンクを集約した上で、さまざまなヒントを追加しています。
HTTP のパフォーマンスLink to this heading
ミドルウェア (Middleware)Link to this heading
Django は、サイトのパフォーマンス改善に役立ついくつかの ミドルウェア を用意しています。以下のようなものがあります。
ConditionalGetMiddlewareLink to this heading
モダンなブラウザが ETag と Last-Modified ヘッダを元に条件的にレスポンスを GET するためのサポートを追加します。
GZipMiddlewareLink to this heading
すべてのモダンブラウザのレスポンスを圧縮し、帯域幅と転送時間を節約します。ただし、GZipMiddleware は現在セキュリティリスクと見なされており、TLS/SSL によって提供される保護を無効にする攻撃に対して脆弱です。詳細については、 GZipMiddleware の警告を参照してください。
セッションLink to this heading
キャッシュを使ったセッションLink to this heading
キャッシュされたセッションの使用 は、データベースのような遅いストレージソースからセッションデータを読み込む必要をなくし、頻繁に使用されるセッションデータをメモリ内に保存することで、パフォーマンスを向上させるかもしれません。
静的ファイルLink to this heading
静的ファイル (定義により、動的に生成されないファイル) は、最適化を施すのに格好のターゲットです。
ManifestStaticFilesStorageLink to this heading
ウェブブラウザのキャッシュ機能を利用することで、あるファイルを最初にダウンロードした後のネットワークヒットを完全に無くすことができます。
ManifestStaticFilesStorage は、ブラウザが将来の変更を見逃すことなく長期間にわたって安全にキャッシュできるように、 静的ファイル のファイル名に内容依存のタグを追加します。ファイルが変更されるとタグも変更されるため、ブラウザは自動的にアセットをリロードします。
"Minification"Link to this heading
いくつかのサードパーティ製の Django ツールやパッケージは、HTML、CSS、そして JavaScript を最小化する ("minify") 機能を提供します。これらは不要なホワイトスペースや改行、コメントを取り除き、変数名を短縮することで、サイトが公開するドキュメントのサイズを削減してくれます。
テンプレートのパフォーマンスLink to this heading
注意:
{% block %}の使用は{% include %}より高速です。多数の分割されたテンプレートパーツからページを生成すると、パフォーマンスに影響することがあります。
キャッシュテンプレートローダーLink to this heading
cached template loader を有効にすると、パフォーマンスが劇的に改善する場合が多いです。このローダーを使用することで、テンプレートのレンダリングが必要になった場合に、各テンプレートを毎回コンパイルせずに済むようになるためです。
利用可能なソフトウェアの異なるバージョンを使うLink to this heading
現在使用しているソフトウェアの、より性能の高い別バージョンが利用可能かどうかを確認する価値がある場合もあります。
これらのテクニックは、すでに最適化された Django サイトのパフォーマンスの限界に挑戦したい上級ユーザーを対象としています。
しかし、これらはパフォーマンス問題に対する魔法のような解決策ではありませんし、より基本的なことをすでに正しい方法で行っていないサイトに、わずかな利益以上のものをもたらす可能性は低いでしょう。
新しいものは (常にとはいえないけれども) より良いものであるLink to this heading
よくメンテナンスされたソフトウェアの新しいリリースの動作が遅くなることはかなり稀ですが、メンテナーはすべてのユースケースを予測することはできません。そのため、新しいバージョンは性能が向上する可能性が高いことを認識しつつも、常にそうであると仮定するべきではありません。
これは Django 自体にも当てはまります。リリースを重ねるごとに、システム全体にわたって多くの改良が施されていますが、アプリケーションの実際のパフォーマンスを確認するべきです。なぜなら、場合によっては変更によってパフォーマンスが向上するどころか悪化する可能性もあるからです。
Pythonの新しいバージョンや、Pythonパッケージの新しいバージョンも、しばしばパフォーマンスが向上しますが、推測するのではなく、実際に測定してください。
Django テンプレート言語の代替Link to this heading
ほとんど全ての場合において、Django の組み込みテンプレート言語は十分です。しかし、もしあなたの Django プロジェクトのボトルネックがテンプレートシステムにあると見られ、それを改善するための他の機会を使い果たしたのなら、サードパーティ製の代替手段が解決策になるかもしれません。
Jinja2 を使うと、パフォーマンス、特に速度の向上が得られる可能性があります。
代替テンプレートシステムは、Django のテンプレート言語をどの程度共有するかによって異なります。
代替のソフトウェア実装Link to this heading
あなたが使っているPythonソフトウェアが、同じコードをより速く実行できる別の実装で提供されていないか確認する価値があるかもしれません。
しかし、適切に書かれた Django サイトのほとんどのパフォーマンス問題は、Python の実行レベルではなく、非効率なデータベースクエリ、キャッシュ、およびテンプレートにあります。もし、不適切に書かれた Python コードに依存している場合、それがより速く実行されるようになっても、パフォーマンス問題が解決されることはありません。
別の実装を使用すると、互換性、デプロイメント、移植性、メンテナンスの問題が発生する可能性があります。非標準の実装を採用する前に、それが潜在的なリスクを上回る十分なパフォーマンスをアプリケーションにもたらすことを確認する必要があることは言うまでもありません。
これらの注意事項を念頭に置いた上で、次の項を読んでください:
PyPyLink to this heading
PyPy は Python 自身で書かれた Python 実装です ('標準の' Python 実装は C で書かれています)。heavyweight なアプリケーションの場合、PyPy を使用するとパフォーマンスが大幅に向上する可能性があります。
A key aim of the PyPy project is compatibility with existing Python APIs and libraries. Django is compatible with versions of PyPy corresponding to the supported Python versions, but you will need to check the compatibility of other libraries you rely on.
That said, a lot of a web framework's work is done by concatenating strings, and PyPy has an issue with that (see this PyPy blog). This may cause performance issues, depending on your use.
Python ライブラリの C 実装Link to this heading
一部の Python ライブラリは C 言語で実装されており、はるかに高速です。これらは同じ API を提供することを目指しています。互換性の問題や振る舞いの違いが存在することが知られています(そして、それらは常にすぐには明らかにならないこともあります)。