Pythonが好きだ
こんにちは、Laddgeです。
みなさんはどのプログラミング言語を普段使っていますか?
自分はPythonが大好きです。
「ああああああああPythonが好きだあああああああ」
Pythonの弱点
Pythonの弱点といえば、やっぱり実行速度の遅さですよね。
遅さの原因は、Pythonが「インタプリタ言語」であることにありそうです。
プログラミング言語には大きく分けて「コンパイラ言語」と「インタプリタ言語」があります。
コンパイラ言語 | インタプリタ言語 | |
---|---|---|
特徴 | 最初に全てを機械語に変換してから実行する | 一行ずつ機械語に変換しながら実行する |
速さ | 速い | 遅い |
開発のしやすさ | コードを書き換えるたびにコンパイルしなおす必要があってめんどい | コードを書き換えたら即実行できる |
例 | C系, Java, Goなど | Python, JavaScript, PHP, rubyなど |
という感じになっていて、Pythonは右の方ですね。
いちいちコンパイルしなくていいインタプリタ言語は初心者には扱いやすいです。
エラーが起きた時のデバッグもインタプリタ言語の方がやりやすいと思います。
書きやすいのはいいのだけど、実行速度はどうしてもコンパイラ言語には敵いません。
ctypesでCと連携!?
実は、PythonはC言語とめちゃくちゃ相性がよくて、C言語と連携するためのライブラリが標準で用意されています。
それが、「ctypes」です。
これを使うとC言語で書いた関数や構造体などをPythonから使うことができるようになります。
今回はこいつで、重い処理はCに投げちゃえばよくねってのを検証していこうと思います。
いざ検証
検証方法としては、再帰ループでフィボナッチ数列の項を求めるプログラムの実行時間で比較します。
(こういうときに相応しい検証方法かわからないけどとりあえずこれで行きます)
Pythonのバージョンは以下の通り。
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0] on linux
Pythonだけのプログラム
以下のプログラムでいきます。
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
def main():
fib(40)
if __name__ == "__main__":
main()
以下のサイトを参考にしています。
このプログラムでフィボナッチ数列の第40項を求めることができます。
ちなみに答えは102334155ですね。
試しに実行時間を測ってみます。
$ time python3 fib_py.py
結果は、
python3 fib_py.py 31.89s user 0.00s system 99% cpu 31.895 total
32秒ね、まあそんなもんでしょう。
ctypes併用
まずはC言語で再帰ループ部分を作ります。
int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
めっちゃ単純ですね。
今回は標準入出力もないただの関数なので、いつもおまじない的に書いていた#include <stdio.h>
もいりません。
これを共有ライブラリとしてコンパイルします。
$ gcc fib.c -shared -o fib.so
これで、Pythonから読み込める共有ライブラリができました。
追記: コメントで、「コンパイル時に最適化を行うとより速くなる」との声をいただきました。実際に"-O2"オプションをつけてコンパイルしたところ、3.6倍ほど速くなりました。
Python側のプログラムは以下の通りです。
import ctypes
libc = ctypes.cdll.LoadLibrary("./fib.so")
def main():
libc.fib(40)
if __name__ == "__main__":
main()
Cで書いた関数をそのまま使えるのが分かると思います。
こちらも実行時間を測ってみます。
$ time python3 fib_c.py
結果は、
python3 fib_c.py 0.97s user 0.01s system 99% cpu 0.974 total
1秒かからんかった。すげぇ。
比較
最後に、同じ条件でちゃんと比較したいので、比較用のスクリプトを書いていきます。
import time
import fib_py
import fib_c
py_start = time.time()
for _ in range(10):
fib_py.main()
py_time = time.time() - py_start
c_start = time.time()
for _ in range(10):
fib_c.main()
c_time = time.time() - c_start
print("python: {}s, ctypes: {}s".format(round(py_time, 2), round(c_time, 2)))
さっきの処理を10回繰り返すのにかかる時間を計測します。
結果は、
python: 316.07s, ctypes: 9.48s
差は歴然ですね。
Python単体だと5分かかってるのに、ctypes併用では10秒以内に抑えています。
結論
ctypes便利ですね。
今後は、メインの処理をPythonで書いて、重い処理はC言語で、みたいな臨機応変な対応ができそうです。
ctypesにはこれ以外にも色々できることがあるので調べてみてください!