23Pythonプログラムが遅い!高速化したい!そんな時は...PythonC言語ライブラリCython高速化Pythonプログラムが遅い!高速化したい!そんな時は...Pythonで実装したときに、処理時間が長すぎて要件を満たせないことがあります。そんなときにはこの記事で示す4種類の対策があります。高速化のために何かをすると別の何かを失います。メモリ使用量、CPU使用量でしょうか。用法・容量を守って正しくお使いください。高速化をする前にはスクリプトのどこが遅いのか、プロファイリングツールなどを使って確かめる必要があります。遅くない処理を頑張って高速化しても、全体の実行時間にはほとんど影響を与えません。この記事ではその手順については説明しません。4種類の高速化手法この記事では、下記の4種類の高速化手法について紹介します。言語仕様・標準ライブラリの範疇でスクリプトを書き直す本来は「ライブラリを使う」や「ライブラリを作る」といったほかの対策も「スクリプトを書き直す」に該当します。ここではPythonの言語仕様と標準ライブラリの範疇でできることとして、下記の手法を紹介します。標準ライブラリの範疇でできることなので、依存はあまり増えません(=Pythonのバージョンには依存することがあります)。Pythonスクリプトの書き方を見直すPythonのインタプリタの最適化はそれほど頑張ってくれません。同じことをするにもいろいろな書き方があるので、より高速な書き方を選んで高速化することができます。実際のアプリケーションではここで例を挙げる書き方の違いでそんなに差は出ません。書き方の変換が思いつかない場合や遅い処理はほかの場所にあることが多いはずです。ちりも積もればなんとやらです。Pythonのみでデータベースの実装をしようと思うと、ここで例を挙げるような処理の遅さが効いてきて、全体としても使い物にならないくらい遅くなってしまいます。1からnまでの整数を足す処理を、「(1)forを使って足していく」「(2)ビルトインのsumを使う」「(3)総和の公式を使う」の3通りで実装して、時間を計ってみると下記のようになります。each)この例では、総和公式が高速なのですが、常にこんな変換を思いつくとは限りません。リストの生成リストを生成するときに、「(1)リスト内包表記を使う」「(2)forループでappendする」「(3)appendをキャッシュする」「(4)ビルトインのlist関数を使う」の4通りで実装して、時間を計ってみると下記のようになります。each)この例では、リストを生成しているだけですが、一般的にはappendする前にいろいろな処理をするはずです。いろいろな処理のほうが遅いことが多いので、いろいろな処理を高速化したほうが効果があります。アルゴリズムとデータ構造を工夫するアルゴリズムとデータ構造は、大昔から研究されている分野です。計算量がオーダー記法で$O(n)$と$O(n^2)$では、実行時間も全然違います。アルゴリズムとデータ構造を自分で実装することもあれば、標準ライブラリにある便利なクラスを使う場合こともあります。ここでは、標準ライブラリにあるアルゴリズムとデータ構造について例を挙げます。データの探索リストに特定の要素が含まれているかを何回も判定するときは、リストを集合(set)に変換してからinで確かめたほうが早いです。1回しか判定しないのであれば、集合に変換する処理時間が効いてくるでしょう。each)今回は、10000個の要素からなるリストに対して、10000回検索するだけでこの結果となりました。これくらいの量の探索なら、実際のアプリケーションでもよく出てくるのではないでしょうか。データの操作リストの先頭に何度も値を追加したい時にはcollections.dequeを使うことができます。each)Pythonのリストは、可変長配列のようなものです。配列の中身については、型や順序など何の前提もないので汎用性は高いですが、特定の処理では高速化・効率化の余地があります。collections.dequeのほかにもheapqやbisectなどコレクション(データの集まり)を扱う機能が標準ライブラリに用意されています。キャッシュを活用する一度行った計算の結果をメモリ内に格納して再利用したり、データベースからとってきたデータをpickle形式でdumpすることで、高速化することができます。数学的な関数のような引数が一緒ならば常に同じ結果を返すものは、キャッシュすることができます。データベースと通信するよりも、ローカルファイルを読み込んだほうが高速な場合もあります。関数のメモ化Pythonにはfunctools.lru_cacheという関数をメモ化するためのデコレータが標準ライブラリに定義されています。これを使えば一度計算した結果がキャッシュされ、引数が同じであれば再利用されます。each)@lru_cacheデコレータを付ければいいだけなのでお手軽ですね。実際にフィボナッチ数列を計算したいことはあまりないですし、計算したいとしても別の実装にするかもしれません。機械学習で積分を計算したい時などが代表的な応用例でしょうか。並列化する標準ライブラリのthreadingやmultiprocessingを使ったり、Python3.2で追加されたconcurrentを使って並列化することで高速化します。CPUがボトルネックでなければthreading、そうでなければmultiprocessingを使います。Lock)によりのCPU100%の壁を越えられないため、マルチコアCPUを使い切りたいのであればmultiprocessingを使うことになります。concurrentライブラリconcurrent.futureを使うのが簡単です。ProcessPoolExecutorを使えば、マルチプロセスで並列実行できます。マルチスレッドで実行するThreadPoolExecutorもあります。futures]並列化したい処理を関数化して、ワーカー数を指定して呼び出すだけです。定型句として覚えてしまいましょう。Pythonスクリプトマルチプロセスにするのであれば、Pythonではなくshellで頑張ることもできます。前述の関数fをコマンドラインから実行可能にして、shellから並列に呼び出してあげることでマルチプロセスが実現できます。入力ファイルが下記(input)だとします。input0123456789splitコマンドで適当に分割します。input分割したファイルを入力として、Pythonスクリプトを実行します。done分割された実行結果を、結合します。resultPythonの中で全部実現することもできますが、shellで&を付けるだけで気楽にマルチプロセスにするのも状況によってはありだと思います。ライブラリを使うPythonには標準ライブラリがたくさんありますし、サードパーティのライブラリはもっとたくさんあります。サードパーティのライブラリは、PyPIで公開されており、pipコマンドで簡単にインストールすることができます。ライブラリを使うNumPyのような数値計算に特化したライブラリを使えば高速に数値計算できます。数値計算に限らず、特定の計算に対して最適化されたライブラリがあります。CuPyは、CUDAを使ってGPUによる計算をPythonから簡単に実行できるようにしてくれます。NumPyNumPyを使うと行列やベクトルの計算が高速化できます。高速化できるだけでなく、コードが簡略になってわかりやすくなります。ここでは、100×1000の行列をイメージして100000個の要素からなるリストの各要素を足しています。each)NumPyのほうが圧倒的に早いことがわかります。リストでは、要素の型やリストのメソッドが変わってしまうことを考慮しますが、NumPyでは型が固定されているのとBLASなどのライブラリを使えるため高速化できます。pickleとcPickleライブラリが何で実装されているかでも、速度に差ができます。2系ではpickle化にはpickleとcPickleという二つのライブラリがありました。どちらを使うかで実行時間に結構な差が出ます。3系では、cPickleは_pickleライブラリという名前に代わってpickleをインポートしたときにcPickleが利用可能であれば内部的に呼び出されるようになりました。2系のIPythonなので表示が他と違いますが、10倍くらいの実行時間比ですね。ライブラリの環境変数やコンパイル条件を見直す最近のCPUにはいろいろな高速化機能があります。高速化機能自身が有効になっているか、高速化機能をうまく使えるライブラリを呼んでいるかで、実行時間に差ができます。下記のページに解説があります。ライブラリを作るすでにPythonライブラリがあればインストールして使うだけでいいのですが、やりたいことに対応するPythonライブラリが必ずしもあるとは限りません。Pythonで書いたのではどうしても解消できない無駄な処理も結構あります。それでも高速化したい場合は、下記のいずれかの手法をとることになります。C言語で処理を実装しctypesで呼び出すPythonには、WindowsのdllファイルやLinuxのsoファイルからライブラリを読み込んで、ライブラリ内の関数を実行する機能(ctypes)が標準ライブラリあります。Pythonで書くと遅い処理を高速に実行する実装がすでにあるけれど、Pythonから呼び出せるようになっていない場合に使うことができます。ctypesPythonのmathライブラリには、立方根を求めるcbrt関数はありませんが、libmには定義されています。これを呼び出すには下記のようにします。2.0この例では嬉しさはわかりませんが、簡単に呼べることだけはおわかりいただけたかと思います。Python側でforを回すと遅いので、forで回す処理も実行してくれるような関数を呼び出せると高速化できます。C言語でPythonライブラリを作るPythonのC拡張を使うと、C言語やC++言語を使ってPythonから呼び出せるライブラリを作ることができます。PythonはそもそもC/C++で作られたライブラリのラッパみたいなものなので、ラッパに呼び出される側を作ろうということです。C言語でPythonから呼び出せる足し算関数を実装するC拡張で足し算関数を作ってみます。C言語で下記のソースコードを書きます。m;}Pythonのヘッダファイルを指定して、gccでコンパイルします。samplemodule.csample.soが出来上がるので、Pythonインタプリタ(IPython)を起動して読み込んでみます。Boost.Python(C++のライブラリ)を使うBoost.Pythonを使うと、少ないコードでPythonライブラリを実装できます。Pythonのバージョン差異もあまり気にしなくてよくなります。その代わり、Boostライブラリに依存してしまうため、Boostライブラリがない環境ではPythonライブラリが読み込みません。Boost.Pythonを使って作られたライブラリは、実装の方法にもよりますがC言語で実装した場合と同じくらい高速です。BoostでPythonから呼び出せる足し算関数を実装するBoost.Pythonで足し算関数を作ってみます。C++言語で下記のソースコードを書きます。&add);}PythonのヘッダファイルやBoost.Pythonを指定して、g++でコンパイルします。sample.cppsample.soが出来上がるので、Pythonインタプリタ(IPython)を起動して読み込んでみます。3C言語で一から書くのに比べてとてもコード量が短くなりました。Cythonを使うCythonを使えば、Pythonをちょっと改造した言語でソースコードを書いて、C言語で書いたものと同じくらい高速なライブラリを生成することができます。手順は、Pythonライクな言語でpyxファイルを作成し、C言語のソースコードを生成し、それをコンパイルするという流れになります。Cythonでフィボナッチ関数を実装するフィボナッチ数列を計算する関数をCythonで作ってみます。pyxファイルを作成します。型の情報があるくらいで、ほぼPythonと同じ文法です。fib(n-1)cythonコマンドでpyxファイルをC言語のソースコードに変換します。gccでコンパイルします。fib.cfib.soが出来上がるので、Pythonインタプリタ(IPython)を起動して読み込んでみます。ms」だったのでかなり改善されました。バイトコードを最適化するここまでは、C言語でライブラリを作成して高速化していました。ライブラリを作成するにはC言語やCythonによるPythonを拡張した言語の知識が必要で、Python以外の言語を学ぶ必要がありました。新しい言語を学ぶのはなかなか大変です。ここでは既存のPythonプログラムをできるだけそのままで拡張できる下記の手法を紹介します。IRからバイトコードを生成して高速化します。Qiitaにあるように、PythonはVM型のスクリプト言語で中間命令(バイトコード)を愚直に実行することで処理を実現します。本家サイトのガイドによると、バイトコードを最適化するだけでなくマシン命令を生成し関数呼び出しを置き換えるようです。PyPyを使うPyPyは、Pythonで書かれたPython処理系です。RPythonという、Pythonより少し機能が制限されていて最適化がしやすいPythonが動作します。RPythonで実装されており、Python2.7とPython3.6と互換性があります。Speedによると実行速度もなかなか優秀なようです。コメントに基づき一部文言を修正しました。最後にこの記事では下記の高速化手法を紹介しました。PyPyを使う遅いと感じた処理への対応としてどの手法が適切かは時と場合によります。いろいろな選択肢を考慮したうえで、最適な手法を適用できると素敵ですね。フィボナッチ数列の例ばかり出していますが、フィボナッチ数列を求めるのがアプリケーションの一般的なタスクではありません。高速化したい処理によってはNumbaよりCythonのほうが早いこともあるでしょう。今回フィボナッチ数列を求めるのに再起を使っていますが、そもそも再起を除去すればもっと高速になりますね...。Lifeが素晴らしいものになりますように。参考この記事を書くにあたって下記の記事を参考にしました。Notebookなお、私がこれまでPythonを学ぶにあたっては、上記以外の素敵な記事や書籍にお世話になっています。ここで紹介しきれないことを大変申し訳なく思っています。素晴らしい情報を公開・出版してくださり、ありがとうございます。その素晴らしい情報が、これからPythonを学ぶ方々の役に立つと信じています。横浜事業所で、データベース(HiRDB/HADB)を開発する部署の隣で、自社データベースを活用するサービス・ソリューションを作っています。好きな言語はPythonとSQL、これから学びたい言語はRustです。会社の音楽隊にも所属して、フルートを吹いたりしています。OSSソリューションセンタ社内でのOSS活用推進や、OSSコミュニティ活動などを行っています。upLoginユーザーは見つかりませんでした。