パフォーマンスと最適化

このドキュメントでは、Django コードをより効率よく、早く、より少ないリソースで実行するためのテクニックとツールについて、その概要を説明します。

はじめに

一般に、最も関心があるのは、コードが正しく 動作する、つまり、書かれたコードのロジックがが期待通りの出力を生成することです。しかし、それだけでは、期待通りに 効率よく 動作しているとは言えません。

この場合、必要なのは、コードの振る舞いに影響を与えることなく、あるいは影響を最小限に保ちつつ、そのパフォーマンスを向上させる実用的な方法です。

一般的なアプローチ

何のために 最適化をしようとしているのか?

最適化する「パフォーマンス」という言葉で何を意味しているのか、はっきりと考えておくことが大切です。パフォーマンスを測る指標は1つではないからです。

高速化というものがプログラムのために最も明らかな目的かもしれません。しかし、他の観点からパフォーマンスを向上することもできます。たとえば、メモリー消費量を少なくするとか、データベースやネットワークのアクセス量を削減するといったことが考えられます。

1つの領域を改善すると、別の領域の性能も向上することがよくありますが、常にそうとは限らず、一方が他方を犠牲にすることさえあります。たとえば、プログラムの速度の改善がメモリ使用量の増加を引き起こすかもしれません。さらに悪いことに、それが自滅的になるかもしれません。もし速度の改善がメモリを大幅に消費してしまったら、システム上のメモリが足りなくなり、結果的に利益よりも害を与えることになってしまうでしょう。

他にも気に留めておく必要があるトレードオフがあります。あなた自身の時間が CPU 時間以上に貴重なリソースだということです。一部の改善は実装する価値がないほど難しすぎるかもしれず、ポータビリティやコードメンテナンスに影響を与えてしまうかもしれません。すべての性能改善に努力する価値があるわけではないのです。

そのため、何を目的としたパフォーマンスの改善なのかを知っておくべき必要があります。また、その目的とした方針に相応の理由があるかを知っておく必要もあります。

パフォーマンスのベンチマーク

コード中で効率の悪い部分を、単に想像したり当て推量したりするというのは、あまり良い考えではありません。

Django のツール

django-debug-toolbar is a very handy tool that provides insights into what your code is doing and how much time it spends doing it. In particular it can show you all the SQL queries your page is generating, and how long each one has taken.

サードパーティ製のパネルをツールバーに追加することも可能です。それにより、たとえば、キャッシュのパフォーマンスや、テンプレートのレンダリング時間を測定できます。

サードパーティのサービス

サイト上のページのパフォーマンスを分析・レポートしてくれる無料のサービスが数多く存在します。これらのサービスでは、実際のユーザーエクスペリエンスをリモートの HTTP クライアントの観点からシミュレートしてくれます。

これらのサービスは、コードの内部について報告することはできませんが、Django 環境内部からでは十分に測定できない観点も含めて、サイト全体のパフォーマンスについて役に立つ見解を提供してくれます。例えば、次のようなサービスがあります。

同様の分析を実施する有料サービスもいくつかあり、一部は Django を認識するため、コードベースと統合して性能のプロファイルをより包括的に得られます。

最初から物事を正しく行う

最適化には、パフォーマンスの悪い部分を改善する作業も含まれますが、パフォーマンスの向上を考える前に取り入れるべき点として、どんな場合でも作業に採用できる良い習慣があります。

この点においてPython は優れた言語であり、エレガントに見え、正しいと感じる解決策が通常、最も性能が良いものです。ほとんどのスキルと同様に、「正しいと見える」ものを学ぶには実践が必要ですが、最も有用なガイドラインの一つはこれです:

適切なレベルで作業する

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 }}

通常、その仕事に最も適したレベルは、コードを書くのに快適な最も低いレベルのものです。

注釈

上記の例は単なる例示に過ぎません

まず第一に、実際のケースでは、カウントの前後に何が起こっているかを考慮し、 その特定のコンテキストにおいて 最適な方法を見つけ出す必要があります。データベースの最適化に関するドキュメントでは、 テンプレートの中でカウントした方が良い場合 について説明しています。

次に、他の選択肢も考慮すべきです。実際のケースでは、テンプレートから直接 QuerySetcount() メソッドを呼び出す {{ my_bicycles.count }} が最も適切な選択かもしれません。

キャッシュ

値の計算は高コストになる(つまり、リソースを多く消費し、遅い)ことが多いので、その値をすぐにアクセス可能なキャッシュに保存しておくことで、次に必要になる時のための大きなメリットになります。

これは十分に重要で強力な技術なため、Djangoには包括的なキャッシュフレームワークと他の小さなキャッシュ機能の部品が含まれています。

キャッシュフレームワーク

Django の キャッシュフレームワーク は、動的なコンテンツを保存して、リクエストごとに再計算しないで済むようにすることで、パフォーマンスを向上するための非常に大きな可能性を提供します。

利便性のため、Django は様々なレベルのキャッシュの粒度を提供しています。特定のビューの出力をキャッシュしたり、生成するのに時間のかかるパーツだけをキャッシュしたり、あるいは、サイト全体をキャッシュすることでさえ可能です。

キャッシュの実装は、書き方が良くないことが原因でパフォーマンスが悪いコードを改善する手段として考えるべきではありません。キャッシュは、パフォーマンスの良いコードを生み出すための最終段階の一つであり、近道ではありません。

cached_property

クラスのインスタンスのメソッドを複数回呼ぶ必要があるというのはよくあることです。その関数の実行が高コストである場合、複数回の呼び出しは無駄が多いです。

cached_property デコレータを使うことで、プロパティが返す値を保存して、次回同じインスタンスで関数が呼ばれた時、その値を再計算する代わりに、保存しておいた値を返すようにすることができます。ただし、このデコレータが機能するのは、メソッドが引数として self のみを取り、メソッドをプロパティーに変えられる場合のみなので、注意してください。

特定の Django コンポーネントは、独自のキャッシュ機能を実装しています。以下のそれらのコンポーネントに関係するセクションで解説します。

遅延について理解する

遅延 (laziness) とは、キャッシュ機能を補完するもう1つの戦略です。キャッシュは結果を保存することで再計算を防ぎますが、遅延は実際に値が必要になるまで、計算の実行を遅らせます。

遅延によりインスタンス化される前に、またはインスタンス化が可能になる以前であっても参照できるようになります。これにはさまざまな用途があります。

たとえば、遅延翻訳 は対象となる言語自体が判別される前に使用できます。翻訳後の文字列がレンダリングされたテンプレートなどで実際に必要になるまで使用されなくなるためです。

そもそも怠惰(Laziness)とは、仕事を避けることで労力を節約することでもあります。つまり、怠け癖の一面は、やらなければならないことがあるまで何もしないことです。そのため、怠け癖はパフォーマンスにも影響し、高価な仕事であればあるほど、怠け癖によって得られるものは大きくなります。

Python は特に generatorgenerator expression を使って、遅延評価のための多くのツールを提供します。あなたのコードで遅延パターンを利用する機会を見つけるために、Pythonの遅延(Laziness)について読む価値があります。

Django における遅延

Django は遅延をうまく使います。このよい例として、QuerySet の評価が挙げられます。QuerySet は遅延評価されます。そのため、QuerySet は作成後、記述したアイテムを取得するためにデータベースへのアクセスを一切行うことなく、さまざまな場所に渡したり、他の QuerySet と組み合わせることができます。渡されるのは QuerySet オブジェクトであって、最終的にはデータベースから取得されるアイテムのコレクションではないのです。

他方で、特定の操作は QuerySet の評価を強制しますQuerySet の時期尚早な評価を回避することで、高コストで不必要なデータベースへのアクセスを節約できます。

Django には keep_lazy() デコレータもあります。これにより、遅延引数で呼び出された関数は、必要なときだけ評価されるようになります。このため、遅延引数 (高コストな引数である可能性もあります) は、必要なときだけ評価されるようになります。

データベース

データベースの最適化

Djangoのデータベース層は、開発者がデータベースを最大限活用できるように、さまざまな方法を提供しています。 データベースアクセスの最適化ドキュメント では、データベースの利用を最適化する際にとるべき手順を、大まかにいくつかの見出しとしてまとめています。それぞれの見出しの下に、関連するドキュメントへのリンクを集約した上で、さまざまなヒントを追加しています。

HTTP のパフォーマンス

ミドルウェア (Middleware)

Django は、サイトのパフォーマンス改善に役立ついくつかの ミドルウェア を用意しています。以下のようなものがあります。

ConditionalGetMiddleware

モダンなブラウザが ETagLast-Modified ヘッダを元に条件的にレスポンスを GET するためのサポートを追加します。

GZipMiddleware

すべてのモダンブラウザのレスポンスを圧縮し、帯域幅と転送時間を節約します。ただし、GZipMiddleware は現在セキュリティリスクと見なされており、TLS/SSL によって提供される保護を無効にする攻撃に対して脆弱です。詳細については、 GZipMiddleware の警告を参照してください。

セッション

キャッシュを使ったセッション

キャッシュされたセッションの使用 は、データベースのような遅いストレージソースからセッションデータを読み込む必要をなくし、頻繁に使用されるセッションデータをメモリ内に保存することで、パフォーマンスを向上させるかもしれません。

静的ファイル

静的ファイル (定義により、動的に生成されないファイル) は、最適化を施すのに格好のターゲットです。

ManifestStaticFilesStorage

ウェブブラウザのキャッシュ機能を利用することで、あるファイルを最初にダウンロードした後のネットワークヒットを完全に無くすことができます。

ManifestStaticFilesStorage は、ブラウザが将来の変更を見逃すことなく長期間にわたって安全にキャッシュできるように、 静的ファイル のファイル名に内容依存のタグを追加します。ファイルが変更されるとタグも変更されるため、ブラウザは自動的にアセットをリロードします。

"Minification"

いくつかのサードパーティ製の Django ツールやパッケージは、HTML、CSS、そして JavaScript を最小化する ("minify") 機能を提供します。これらは不要なホワイトスペースや改行、コメントを取り除き、変数名を短縮することで、サイトが公開するドキュメントのサイズを削減してくれます。

テンプレートのパフォーマンス

注意:

  • {% block %} の使用は {% include %} より高速です。
  • 多数の分割されたテンプレートパーツからページを生成すると、パフォーマンスに影響することがあります。

キャッシュテンプレートローダー

cached template loader を有効にすると、パフォーマンスが劇的に改善する場合が多いです。このローダーを使用することで、テンプレートのレンダリングが必要になった場合に、各テンプレートを毎回コンパイルせずに済むようになるためです。

利用可能なソフトウェアの異なるバージョンを使う

現在使用しているソフトウェアの、より性能の高い別バージョンが利用可能かどうかを確認する価値がある場合もあります。

これらのテクニックは、すでに最適化された Django サイトのパフォーマンスの限界に挑戦したい上級ユーザーを対象としています。

しかし、これらはパフォーマンス問題に対する魔法のような解決策ではありませんし、より基本的なことをすでに正しい方法で行っていないサイトに、わずかな利益以上のものをもたらす可能性は低いでしょう。

注釈

繰り返しになりますが すでに使用しているソフトウェアの代替を探すことは、パフォーマンス問題に対する最初の答えでは決してありません 。このレベルの最適化に達すると、正式なベンチマーク・ソリューションが必要になります。

新しいものは (常にとはいえないけれども) より良いものである

よくメンテナンスされたソフトウェアの新しいリリースの動作が遅くなることはかなり稀ですが、メンテナーはすべてのユースケースを予測することはできません。そのため、新しいバージョンは性能が向上する可能性が高いことを認識しつつも、常にそうであると仮定するべきではありません。

これは Django 自体にも当てはまります。リリースを重ねるごとに、システム全体にわたって多くの改良が施されていますが、アプリケーションの実際のパフォーマンスを確認するべきです。なぜなら、場合によっては変更によってパフォーマンスが向上するどころか悪化する可能性もあるからです。

Pythonの新しいバージョンや、Pythonパッケージの新しいバージョンも、しばしばパフォーマンスが向上しますが、推測するのではなく、実際に測定してください。

注釈

特定のバージョンで異常なパフォーマンスの問題に遭遇しない限り、通常新しいリリースはより優れた機能、信頼性、セキュリティ性能を持ち、これらの利点は、あなたが得るか失うかもしれないパフォーマンスよりもはるかに重要です。

Django テンプレート言語の代替

ほとんど全ての場合において、Django の組み込みテンプレート言語は十分です。しかし、もしあなたの Django プロジェクトのボトルネックがテンプレートシステムにあると見られ、それを改善するための他の機会を使い果たしたのなら、サードパーティ製の代替手段が解決策になるかもしれません。

Jinja2 を使うと、パフォーマンス、特に速度の向上が得られる可能性があります。

代替テンプレートシステムは、Django のテンプレート言語をどの程度共有するかによって異なります。

注釈

テンプレートでパフォーマンスの問題が発生した場合、最初に行うべきことは、その理由を正確に理解することです。代替のテンプレートシステムを使用することで速度が向上するかもしれませんが、その手間をかけずに同じ利益を得ることも可能です。たとえば、テンプレート内の高コストな処理やロジックは、ビュー内でより効率的に行うことができます。

代替のソフトウェア実装

あなたが使っているPythonソフトウェアが、同じコードをより速く実行できる別の実装で提供されていないか確認する価値があるかもしれません。

しかし、適切に書かれた Django サイトのほとんどのパフォーマンス問題は、Python の実行レベルではなく、非効率なデータベースクエリ、キャッシュ、およびテンプレートにあります。もし、不適切に書かれた Python コードに依存している場合、それがより速く実行されるようになっても、パフォーマンス問題が解決されることはありません。

別の実装を使用すると、互換性、デプロイメント、移植性、メンテナンスの問題が発生する可能性があります。非標準の実装を採用する前に、それが潜在的なリスクを上回る十分なパフォーマンスをアプリケーションにもたらすことを確認する必要があることは言うまでもありません。

これらの注意事項を念頭に置いた上で、次の項を読んでください:

PyPy

PyPy は Python 自身で書かれた Python 実装です ('標準の' Python 実装は C で書かれています)。heavyweight なアプリケーションの場合、PyPy を使用するとパフォーマンスが大幅に向上する可能性があります。

PyPy プロジェクトの主目的の1つは、既存の Python API とライブラリとの 互換性 です。Django は互換性がありますが、他の依存ライブラリの互換性については確認が必要です。

Python ライブラリの C 実装

一部の Python ライブラリは C 言語で実装されており、はるかに高速です。これらは同じ API を提供することを目指しています。互換性の問題や振る舞いの違いが存在することが知られています(そして、それらは常にすぐには明らかにならないこともあります)。

Back to Top