Pythonはインタープリタ言語であり、柔軟なデータ型を持つため、Pythonの構文のみで構成されたループ処理などは、処理速度が遅くなりがちです。特に大量のデータを扱う場合は、処理の遅さが顕著になります。
このような問題に対し、CythonはPythonコードをC言語に変換してコンパイルすることで、インタープリタ特有のリアルタイムな解析処理を回避し、全体的なパフォーマンスを向上させることができます。
今回は、Pythonで定義された関数をCythonを用いてコンパイルし、Pythonから呼び出す方法について解説します。
Cythonとは
CythonはPythonコードをCに変換し、コンパイル(ビルド)することで高速化を図る拡張言語です。以下の特徴があります:
- Pythonの文法に近い記述が可能
- 型指定によるコンパイル最適化
- NumPyとの親和性が高い
- Cライブラリとの連携が容易
実行環境によって異なりますが、例えば ythonとCythonの処理速度は以下の差があります。
処理内容 | 純粋Python | Cython使用時 |
---|---|---|
1億回の加算ループ | 約4.2秒 | 約0.8秒 |
PandasやNumpyなど、もともとC言語で作られているモジュールはCythonで改善できません。あくまでもPythonで作られた純粋なソースコードが高速化の対象となります。
Cythonの使い方
以下の手順に従って進めれば、簡単にCythonが使えるようになります。
あらかじめ、pip コマンドで cython をインストールしておきます。
pip install cython
高速化したいPythonの関数、又はクラスを記述し、拡張子を .pyx で保存します。
def multiply(int a, int b):
return a * b
setup.py という名前でファイルを作成し、次の内容を記述します。example.pyx の部分は、STEP.2で保存したファイル名を指定してください。
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("example.pyx")
)
コマンドプロンプトから setup.pyと .pyx が置かれているフォルダに移動し、下記のコマンドを実行します。
python setup.py build_ext --inplace
ビルドに成功すると、2つのファイルと1つのフォルダが出来上がります。例えば、example.pyx という名前のファイルをコンパイルすると、以下のフォルダとファイルが出来上がります。

ここまで問題なく成功したら、モジュール名(ここでは example)と関数名(ここでは multiply)をインポートするだけで使うことができます。
from example import multiply
result = multiply(3, 5)
print(result) # 出力: 15
.pyxに記述するPythonプログラムの書き方
.pyx
ファイルには、通常の Python コードをそのまま記述することも可能ですが、必ずしも十分な高速化が得られるとは限りません。
これは、Cython が Python のコードを C/C++ に変換・コンパイルして実行速度を向上させる仕組みであるため、どのように書かれているか=変換結果によって、パフォーマンスに大きな差が出るからです。
Cython の詳細な構文や高度な記述方法については、公式ドキュメント「Welcome to Cython’s Documentation」を参照いただくとして、ここでは実践上知っておくべき重要なポイントに絞って解説します。
通常の Python より厳格な構文が必要
Python の緩い構文とは違い、Cython では型宣言の位置や文法が明確である必要があります。
cdef int i # 明示的な型定義
Pythonの自由度に慣れていると、構文エラーで足止めを食うことが多くなりがちなので、気を付けましょう。
cdef
/ cpdef
/ def
を使い分ける
通常の関数は def
で定義できますが、Cythonで拡張された cdef
や cpdef
を使うと、高速化や C 言語との連携が可能になります。この時、Python 側から関数を呼び出したい場合は、def
または cpdef
で定義する必要があります。cdef
は高速ですが、Python からは見えないため、外部公開が必要な関数には適しません。
修飾子 | Python から呼び出し | Cython 内で使用 | 高速性 | 用途例 |
---|---|---|---|---|
def | 〇 | 〇 | ◯ | 通常のPython関数 |
cdef | × | 〇 | ◎ | 内部専用の高速処理関数 |
cpdef | 〇 | 〇 | ◎ | 双方から使いたい関数(公開) |
ループ処理を含む関数は cdef
で定義することで、C レベルで最適化され、cpdef
よりも高速に実行されることが期待できます。ただし、cdef
関数は Python から直接呼び出せないため、Cython 内部専用の処理として設計する必要があります。
cdef list calc_with_cdef(int n):
cdef int i
cdef list result = []
for i in range(n):
result.append(square_c(i))
return result
型指定でパフォーマンスを引き出す
cdef
は関数だけでなく、変数を定義する際にも使用されるキーワードです。Cython の大きな魅力のひとつは、明示的な型指定によって C 言語並みの高速処理が可能になる点にあります。
この恩恵を最大限に活かすには、変数に明確なデータ型を指定する必要があります。
Cython では cdef
を用いることで、int
や double
、char
のような C 言語由来の低レベルな型を利用できるようになります。一方で、型を指定しない変数(たとえば x = 1
のようなPython風の記述)は、Python の動的な型(PyObject)として扱われ、Cython 本来の高速化効果は得られません。
cdef int i, j
cdef double val[5]
for i in range(1000000):
for j in range(5):
val[j] += 1
型指定ありと 型指定なしでは、次のような違いがあります。
特性 | Python 変数 (x = 1) | cdef 変数 (cdef int x = 1) |
---|---|---|
型 | 動的(PyObject) | 静的(Cレベル int/float 等) |
実行速度 | 遅い(仮想マシン経由) | 速い(Cコードに直接変換) |
GIL(Global Lock) | 必須 | GILなしで処理可能(条件による) |
型チェック | 実行時に行われる | コンパイル時に固定 |
最適化のしやすさ | 難しい | 非常に最適化しやすい |
メモリ使用 | 大きめ(PyObjectが必要) | 小さい(Cスタック上で済む) |
Pythonの機能が一部制限される
Cython は C/C++ に変換されるため、Python の一部機能や標準ライブラリに制約が生じる場合があります。
with
文、ジェネレータ式、リスト内包表記などは 限定的なサポート にとどまります- 正規表現ライブラリ(例:
re
)や動的インスペクションを行うinspect
モジュールは 意図通りに動作しない場面 があります NumPy
などの外部ライブラリを高速に扱うには、cimport
を使って Cython 向けの専用 API を明示的に利用する必要があります
下記は、NumPy + cimport
の基本的なサンプルです。
# distutils: language = c
# cython: boundscheck=False, wraparound=False
import numpy as np
cimport numpy as cnp
def sum_array(np.ndarray[cnp.float64_t, ndim=1] arr):
cdef Py_ssize_t i, n = arr.shape[0]
cdef double total = 0
for i in range(n):
total += arr[i]
return total
Cython内で標準関数を呼び出す処理は速くならない
str(val)
や len(obj)
、dict.items()
などの標準関数は、Python の仮想マシン(VM)を経由して実行されるため、C レベルの最適化が効きません。
これらは内部的に 動的ディスパッチ(型チェック+関数呼び出し) を伴うため、cdef
で型指定していても高速化の恩恵を打ち消してしまうことがあります。
また、Python オブジェクトを操作する関数は GIL(Global Interpreter Lock)に依存しやすく、nogil
や並列化の恩恵を受けにくいという制約もあります。
Cython 内で処理を高速化したい場合は、標準関数の使用を最小限に抑え、C レベルの処理で完結するような設計を心がけましょう。また、可能であれば C に近いデータ構造やロジックに置き換えることで、より高い性能が期待できます。
悪いプログラム例
cpdef list use_python_str(double[:] vals):
cdef Py_ssize_t i, n = vals.shape[0]
cdef list out = []
for i in range(n):
# Pythonのstr()を使用
out.append(str(vals[i]))
return out
良いプログラム例
cpdef list use_c_str(double[:] vals):
cdef Py_ssize_t i, n = vals.shape[0]
cdef list out = []
cdef char buf[32]
for i in range(n):
# Cのsprintfで文字列化
sprintf(buf, "%.5f", vals[i])
out.append(buf.decode())
return out
標準関数だけでなく、list.append()
や dict[key] = value
は、Python の内部オブジェクト構造とガベージコレクションに依存しているため、Cythonで型を付けても速くならないケースが多いです。
外部 C 関数やヘッダを利用する
Cython は Python コードを C/C++ に変換してコンパイルする機能を持つため、C/C++ の関数やヘッダファイルを直接利用することも可能です。以下は、簡単な C 関数を Cython から呼び出すサンプル構成です。
ここで .pyx
という見慣れない拡張子を持つファイルが登場します。これは、C 言語(または C++)の関数や構造体、型情報などを Cython に伝えるための「宣言ファイル」です。C のヘッダーファイル(.h
)と似た役割を果たし、Cython コード(.pyx
)から C の機能を使えるようにするための「橋渡し」的な存在です。
ヘッダファイル(mymath.h)
// mymath.h
int multiply(int a, int b);
C実装ファイル(mymath.c)
// mymath.c
int multiply(int a, int b) {
return a * b;
}
Cython 用 .pxd ファイル(mymath.pxd)
# mymath.pxd
cdef extern from "mymath.h":
int multiply(int a, int b)
.pxd には、Cythonで使いたいCの関数定義と、その定義が記述されているヘッダファイルを記述します。
Pythonラッパー(example.pyx)
# example.pyx
from mymath cimport multiply
def py_multiply(int x, int y):
return multiply(x, y)
ビルドスクリプト(setup.py)
from setuptools import setup
from Cython.Build import cythonize
from setuptools.extension import Extension
extensions = [
Extension(
name="example",
sources=["example.pyx", "mymath.c"],
include_dirs=["."], # ヘッダファイルの場所
)
]
setup(
ext_modules=cythonize(extensions)
)
setup.py
では、Python ラッパーファイル(.pyx
)と C 実装ファイル(.c
)を明示的に記述しますが、Cython 用の .pxd
ファイルは記述不要です。その理由は、.pxd
は Cython によって自動的に読み込まれる補助的な宣言ファイルであり、ビルド対象そのものではないためです。
通常、.pyx
ファイルから cimport
されると、同じディレクトリ内にある .pxd
ファイルが自動的に参照されます。
おすすめのベストプラクティス
- まずはシンプルな関数やループ処理から
cdef
を試すのがおすすめです。変数の型指定やループの最適化など、小さなスコープから始めることで、効果や挙動を把握しやすくなります。 - Jupyter 環境では
%%cython
マジックを使って素早くプロトタイピングし、動作確認後に.pyx
ファイルに落とし込む流れがスムーズです。試行錯誤を高速に回せます。 - ベンチマークは必ず取りましょう。どの部分が高速化されているのか、また依然としてボトルネックが残っている箇所がどこかを、定量的に把握することが最適化の鍵になります。
まとめ
Python は可読性や柔軟性に優れる一方で、処理速度の面では課題もあります。特にループ処理や型の曖昧さがボトルネックになる場面では、Cython を導入することで劇的な高速化が期待できます。
本記事では、Cython の基本的な使い方から、型指定による最適化、高速な関数定義(cdef
/cpdef
)、NumPy や C 関数との連携方法まで、実践的なポイントを解説しました。
まずはシンプルな関数やループから Cython を適用し、ベンチマークを取りながら効果を確認していくのがおすすめです。Jupyter の %cython
マジックや .pyx
ファイルを使った本格的なビルドまで、用途に応じて段階的に取り入れていくのがお勧めです。
「Pythonの柔軟さ」と「C言語の高速性」の“いいとこ取り”ができる Cython を活用して、ボトルネックの解消と処理性能の向上に挑戦してみてはいかがでしょうか。
コメント