Pythonを高速化しよう!

はじめまして、gumiの津村です。
現在は解析系の仕事をしたり、ツールを作ったりしています。


今回の話は高速化についてです。
結構長めの文章です。

目次

  • 実行速度の高速化
  • Python/C API
  • ctypes
  • Pyrex
  • Cython
  • SWIG
  • その他
  • 纏め

実行速度の高速化

高速化といっても色々ありますが、今回は実行速度の高速化についてです。


弊社ではPythonを全面的に採用していますが、そもそもLLは実行速度が遅い言語です。特にC言語のようなコンパイラ系の言語と比べると非常に遅いです。
それでもLL系の言語がここまで使われるようになったのは、開発効率が良いからです。
もはや常識ですね。


しかし、それでも特定の領域ではどうしてもPythonのようなLL系言語では厳しい部分も出てきます。
アルゴリズムを変更しても、ハードウエアを変えても、無理な物は無理です。


速度に問題がある場合の最適化にはいくつかのアプローチがあります。

  • アルゴリズムの最適化により実行速度をあげる。
  • 現実的に考えて実装、運用が無理な機能は、運用でカバーする。
  • 言語を変える。例えばPythonからC言語に変える等。
  • C/C++でライブラリ作成を行い高速化する。またはそれに伴う各関連技術を使用する。


今回の内容は最後のパターンが話の中心となります。

利点、欠点

その前にこの方法の利点、欠点を挙げておきましょう。
利点

  • Pythonから言語を変える必要が無い
  • C/C++を使用するのでコンパイラ言語と同様の高速な処理ができる


欠点

  • C/C++で作成されたライブラリを使用するにはコンパイルが必要
  • ライブラリにバグがあれば最悪メモリー関係のバグが発生しPythonプログラムごと落ちる
  • 作り次第ではさらに最悪な事も...
  • 仕事ならばLLを使っている会社では、C/C++を使える人が少ないので引き継ぎが問題になる


欠点が多いですが、それでも高速化には非常に大きい魅力が伴います。
欠点に挙げたのは、結局の所C/C++の問題ですので熟練のプログラマならば対処できるはずです。

PythonではC/C++を使うにはいくつか方法がありますのでそれらを紹介していきましょう。

Python/C API

これはPythonが提供しているアプリケーションプログラマ用インタフェースです。
日本語のドキュメントはここから見ることが出来ます。

ソースコード

簡単なプログラムを作ってみましょう。
基本は"Hello World"ですよね。

/* hello.c */
#include "Python.h"

static PyObject * hello(PyObject *self)
{
    printf("Hello World!!\n");
    Py_RETURN_NONE;
}

static char hello_doc[] = "hello module\n";

static PyMethodDef methods[] = {
    {"hello", (PyCFunction)hello, METH_NOARGS, "print hello world.\n"},
    {NULL, NULL, 0, NULL}
};

void inithello(void)
{
    Py_InitModule3("hello", methods, hello_doc);
}
コンパイル && 実行

コンパイル時にはオプションが必要です。
これは私のMac OSX + macportsの環境用です。実際に実行する際には各環境に合わせて修正してください。

$ gcc -Wall -fPIC -c hello.c -I/opt/local/include/python2.5/
$ gcc -shared -o hello.so hello.o -lpython
#hello.py
import hello

hello.hello() #=>Hello World!!
利点、欠点

Python/C APIの利点、欠点を挙げておきましょう。
利点

り易い

  • これは本来の目的から外れますが、Cネイティブ開発でPythonの機能を利用することが出来ます


欠点

  • Python側が決めているルールを色々勉強が必要です
  • Makeファイルから自分で全て書く必要がある
  • Pythonがバージョンアップ等をし、Python側で変更があった場合の対応が大変

ctypes

2.5以上ではctypesというモジュールを使用すると、Cのライブラリを読み込み使用する事が出来ます。

ソースコード

さきほどと同じくHello Worldを作成してみます。

/* hello.c */
#include <stdio.h>

void hello(void)
{
    printf("Hello World!\n");
}
コンパイル && 実行

単なる共有ライブラリを作るのと同じ方法です。

$ gcc -Wall -fPIC -c hello.c
$ gcc -shared -o hello.so hello.o
#hello.py
import ctypes

hello = ctypes.cdll.LoadLibrary('hello.so')
hello.hello() #=>Hello World!
利点、欠点

ctypesの利点、欠点を挙げておきましょう
利点

  • Pythonに標準でついてくる。これは非常に大きな利点です。追加のライブラリが必要ないからです。
  • Python/C APIとは異なり、特殊な知識は必要はありません。もちろんコンパイラやライブラリ関係の知識は必要ですが...
  • 構造体を含めて、Cの型をそのまま扱えます
  • マルチOS対応。WindowsLinuxでも動作するとのことです。私の環境のMac OSXでも動作は確認しています
  • ソースコードPythonのみで完結します


欠点

  • マルチOS対応のプログラムを書く必要がある場合には、LoadLibrary()の部分をPython側でその対応が必要になります
  • 当然、マルチOS対応が難しくなります
  • ソースコードPythonのみで完結する代わり、本来ならば隠蔽すべきローレベルの部分が丸見えになります
  • ソースコードが煩雑になりがちです


ちょっとしたCの関数を呼びたい時に利用するのが良い使い方ではないか、と感じます。
ただし、大規模開発には向かないかなと感じました。

Pyrex

Python/C APIの欠点として拡張モジュールを書くのにはそれなりにCプログラミングの経験が要求されることです。
PyrexはPythonに似た文法ですので、簡単に拡張モジュールが作成できます。
Pyrexの情報はここを参照してください。

インストール

弊社ではvirtualenvを使用していますのでpipでインストールします。virtualenvを使用していな場合はeasy_installでインストールできます。

$ pip install pyrex
$ easy_install pyrex
ソースコード

Hello World程度ならば普通のPythonと代わりがありません。
ちなみに拡張子は".pyx"を使用します。

#hello.pyx

def hello():
    print "Hello World!!"
コンパイル && 実行

pyrexcコマンドを使用するとhello.cが作成されます。
後は普通にコンパイルすれば問題ありません。

$ pyrexc hello.pyx
$ gcc -Wall -fPIC -c hello.c -I/opt/local/include/python2.5/
$ gcc -shared -o hello.so hello.o -lpython
利点、欠点

利点

  • Pythonと文法が似ている
  • Cを習得する必要がない


欠点

  • Pyrexの文法を覚える必要がある。Cを習得するよりは遥かに楽ですが...
  • ソースコードを自動生成するのでPython/C APIと比べて多少効率が悪い(はず)


Cを覚えなくても良いという事は、LLな人には良い選択肢だと思います。
しかし、Cの特徴が消えている(触り辛い)のも否定できません。
構造体が作れるなど一般的な対応は取れているようですが、Cエキスパートな人には物足りないでしょう。

Cython

CythonはPyrexから派生したものです。Pythonと上位互換があります。

インストール

Pyrexと同じくCythonのインストールを事前に行います。

$ pip install cython
$ easy_install cython
ソースコード

Hello World程度ならばPyrexと変わりがありません。
拡張子はPyrexと同じく".pyx"を使用します。

#hello.pyx

def hello():
    print "Hello World!!"


CythonがPyrexと異なるのはコンパイルの為に「setup.py」が必要な事です。

#setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

hello_modules = [Extension("hello", ["hello.pyx"])]

setup(
        name = 'C extention module example',
        cmdclass = {'build_hello': build_ext},
        ext_modules = hello_modules
        )
コンパイル && 実行
$ python setup.py build_ext --inplace
import hello

hello.hello()
利点、欠点

利点

  • Pyrexの上位互換
  • Pyrexよりも高機能


欠点

  • setup.pyが追加で必要


Pyrexでは必要の無いsetup.pyを追加で必要とは言え、Pyrexよりも高機能な点が大きいと感じます。
Pyrexを使うのであれば、上位互換であるCythonを採用するべきでしょう。
またPyrexも含めてですが、中規模開発までならなんとかなるかと思います。

SWIG

SWIGC/C++で書かれたライブラリを様々な言語のライブラリとして利用することが出来ます。
例えば、PerlPHPPythonRubyといったLLからJAVA、C#等にも対応しています。
さらにはTcl/Tk、Lua、Go、Modula-3等といった使用頻度が低い言語までサポートしています。


ctypes、Pyrex、CythonはC言語でライブラリを書く苦労をいかに減らすか、という点を目標にしていると言えます。
しかしSWIGはこれらとは異なり、C/C++でライブラリ開発をいかに開発しやすくするか、という点を目標に置いています。

インストール

SWIGはインストールする必要があります。
パッケージシステム(yum,apt,port等)か、ソースコードからインストールしてください。

ソースコード
/* hello.c */
#include <stdio.h>

void hello(void)
{
    printf("Hello World!!\n");
}


SWIGはライブラリのソースコードと「.i」、「setup.py」が必要です。
hello.i

%module hello
%{
extern void hello(void);
%}
extern void hello(void);
#setup.py
from distutils.core import setup, Extension

hello_module = Extension('_hello',
        sources=['hello_wrap.c', 'hello.c'],
        library_dirs=["/opt/local/lib/"],
        )

setup (name = 'hello',
        version = '0.1',
        author      = "nil",
        description = """hello""",
        ext_modules = [hello_module],
        py_modules = ["hello"],
        include_dirs=["/opt/local/include/"],
        )
コンパイル && 実行
$ swig -python hello.i
$ python setup.py build
$ python setup.py install
#hello2.py

import hello

hello.hello() #=>Hello World!!
利点、欠点

利点

  • Python/C APIを覚えなくても良い
  • あくまでも、Cのライブラリとして実装するのでPythonソースコードを汚さない
  • マルチ言語対応


欠点

  • 「*.i」、「setup.py」が必要で、コンパイルを行う為の設定が面倒
  • hello.so->hello.py->importの順番なので多少無駄がある。


個人的な感想は、マルチ言語対応が大きいと思います。これだけでも他の部分で多少苦労をしても十分ペイすると思います。
また、開発にはそれなりの知識、経験さえあれば、大規模開発も可能と思います。

その他

上記に挙げた以外でも高速化を行う為の方法が用意されています。
調べただけでも、Psyco、Psychotic、boost.python等が見つかりました。

まとめ

いかがだったでしょうか。
ソースコードは入門用のHello Worldでしたが、それぞれの基本的な使い方を説明してきました。
このまま十分応用できるかと思います。


C/C++でライブラリを作成するには敷居が高いですが、その見返りは非常に大きいです。
今回紹介したライブラリにはその敷居を低くするCython等もあります。
また、今回紹介できなかったPsycoも魅力的です。


Pythonはライブラリの多さも魅力の1つで、その中に今回紹介した物も含まれています。
LLでもC/C++に負けない高速化が比較的可能に行えることがお分かり頂けたかと思います。
日本ではPythonを利用する人は少ないですが、この機会にPythonを始めてはいかがでしょうか?


最後になりましたが、コメントやハテぶなどでのご意見もお待ちしています。
より詳しい使い方が知りたい等の意見が多ければ続きも書いていきたいと思います。