Cythonを使うときに気をつけること

[公式のドキュメント](https://cython.readthedocs.io/en/latest/index.html)を読んでもどうしても目から耳へすり抜けてしまうので、ここにまとめておきたいと思います。

# def, cdef, cpdef
正直最初に見たときにはどう使い分けるのやらという感じでしたが、これらは言うなれば、スコープを制御しています：

|スコープ|def|cdef|cpdef|
|-------|---|----|-----|
|Pythonから見える|○|△（`.pyx`ファイル内で使える）|◯|
|Cから見える|×|○|◯|

## defでPython関数定義

まず、`def`はPythonの関数定義に使います。「Pythonの関数」ができないこととして、「Cの関数から呼び出す」ということが挙げられます。C言語側で処理しているときに（たとえば`cdef`された関数の中で）Pythonの関数を呼び出すことはできません。Cのライブラリにコールバック関数として渡してやることもできません。

## cdefでC関数定義

C言語側から呼び出せる関数を定義したい場合には`cdef`を使います。Cythonはこのコードを一旦Cのコードに変換してから、コンパイルします。別の言い方をすると、「Pythonの文法でCのコードを書いている」ようなものです。

ですので`cdef`された関数の中では、以下の項目が必須になります：

+ 全ての変数の（C言語上での）型指定
+ 全ての関数呼び出しが`cdef`等価（`cdef`されているか、Cのライブラリ関数か）

## cdefで変数定義

C言語の型を用いて変数を定義したいときにも、基本的には`cdef`を用います。この場合は`cdef`されたCの関数だけでなく、`def`で作られたPythonの関数の中でも変数の`cdef`を行うことができます。

Python関数内で`cdef`で変数を定義するメリットは、その部分のコードがC言語のアルゴリズムに変換されることです。Python上で`int`の足し算`a+b`を行う場合、`int.__add__(a, b)`を呼び出しているのとほとんど変わらずオーバーヘッドがありますが、C言語上なら直接`a+b`と書かれます。`while`ループなどが入ってインクリメントや条件式がたくさん実行されるようになると、このオーバーヘッドはそれなりに効いてきます。

別のメリットとして、例えばPythonの変数`pyobj`が`int`型であるとあらかじめわかる場合（後述）、Cythonは`cdef int a = pyobj`というコードを「C言語上でのint型への代入」に自動で変換してくれることが挙げられます。これによって、PythonとC言語とをCython上でシームレスに結合することができています。

## cpdefを用いたハイブリッド関数定義

今までのところで、`def`と`cdef`はそれぞれPythonとC言語の関数を定義するのに使えることがわかります。ただ状況によって、PythonのオブジェクトをどうこうできるC言語の関数を作りたいことがあります（コールバック関数など）。そういうときに使うのが`cpdef`です。`cpdef`された関数はPython内での実体を持ちつつ、C言語側からも呼び出せる関数になります。ただPython実体という柔軟性の代償として多少のオーバーヘッドが出ます。

## cdefクラス

関数と同様に、クラス定義でも`cdef`を使うことができます。これは構造体に毛が生えたようなもので、基本的にはCython側から使うことを意図して作られています。

+ Python側から、通常のクラスと同様にインスタンスを作成することができます。
+ インスタンスメソッドは`.pyx`ファイル内で`def`と書くと、基本的に`cdef`と等価になります。
+ インスタンスメソッドは、`built-in function`としてPython側からも呼び出せます。
+ メソッドもインスタンス変数も、基本的にはCython上でしか定義できません。インスタンス変数の初期化および解放は`__cinit__`および`__dealloc__`なる関数で行います（`__init__`や`__del__`は呼び出されない可能性が高い）。
    - メソッドは`cpdef`することで、Python側でもオーバーライド可能なメソッドになります（が、多少遅くなります）。
    - インスタンス変数は`public`宣言することで、読み出しのみの変数としてPythonから見えるようになります。
    - インスタンス変数の読み書きをしたい場合は、プロパティーにします。`@property`あるいは`@xxx.setter`デコレータでメソッドを修飾します。このメソッドは`cpdef`である必要はなく、`def`でも問題ないようです。

# C言語のライブラリを呼び出す

外部のライブラリを使うには、そのヘッダーファイルに何が定義されているか（関数プロトタイプ、構造体etc.）をCythonにわかる方式で記述しておく必要があります。大まかに2種類の方法がありますが、記法はどちらも同じです。

## `.pyx`ファイル内で記述する方法

こちらがより手軽な方法です。イメージとしては「`.pyx`ファイルに対応する名前空間に、ライブラリのシンボルがそのまま取り込まれる」という感じです。

標準ライブラリの場合は、以下のような感じになります：

```cython
cdef extern from "<math.h>":  # 標準ライブラリの場合
    DEF HUGE_VAL = 1e500      # #defineマクロ ('='に注意)
    double exp(double x)      # 関数プロトタイプ
    # 使う関数、マクロだけ定義しておけばよい

# この状態で、同じ.pxdファイル内で HUGE_VALやexpを使うことができる
```

そうでない（普通の）ライブラリの場合は以下：

```cython
cdef extern from "spam.h":
    DEF ARTIFICIAL = 1        # #defineマクロ

    ctypedef int fattype_t    # typedef

    int spam_counter          # グローバル変数

    void order_spam(int tons) # 関数プロトタイプ

    struct spam:              # 構造体定義
       fattype_t fat_type
       double    fat_content
       # アクセスする必要があるメンバだけ定義しておけばよい
```

## `.pxd`ファイルに宣言する方法

Cythonでは、Cライブラリのextern宣言に特化したファイルを`.pxd` (pyrex definitions) という拡張子で作っておくことができます。やることとしては上記と同じで、`.pyx`ファイルに直書きする代わりに`.pxd`ファイルに書き込むだけです。

こちらはどちらかというと、モジュール的な名前空間にCライブラリのシンボルを定義する感じです。こうすることで、どの`.pyx`ファイルからも`cimport ...`の形で読み込めるようになって便利になります。

たとえば`libspam.pxd`を以下のように定義しておきます：

```cython
# in: libspam.pxd
cdef extern from "spam.h":
    void order_spam(int tons)
    struct spam:
        ...
```

こうしておくと、同じディレクトリにある`.pyx`ファイルから、以下のようにCライブラリのシンボルを参照できます：

```cython
# in: spamconsumer.pyx
cimport libspam # libspam.pxdの読み込み

libspam.order_spam(20)   # spam.hの関数order_spamの呼び出し

cdef libspam.spam *STOCK # spam.hの構造体spamの参照
```

色々な`.pyx`ファイルから参照されるようなライブラリの場合、`.pxd`ファイルを作っておいた方が便利です。実際のところ、`cimport numpy`では暗黙的に、Cython側で用意されたnumpy用の`.pxd`を読み込んでいると思います。



## 注１: マクロについて

マクロは、**Cythonで解釈可能な表現(expression)を使って定義する**必要があるようです。なので、場合によってはヘッダーファイルを参照して即値をコピペしなければならない…。マクロ定義された関数をプロトタイプとして表現してよいのか？とか、`NAN`みたいな値はどう定義すればよい？などありますが、試したことがないのでよくわかりません。

## 注２: 構造体について

1. **アクセスするメンバだけ定義しておけばよい**ので、ブラックボックスとして使用するような構造体の場合は`struct Handle: pass`として（メンバ関数の定義なしで）宣言して構いません。
2. **対応ヘッダファイルにシンボルが定義されていればよい**ので、`typdef struct foo { ... } Foo;`で定義されている場合、以下のいずれでもコンパイルは通ります：
    1. `struct foo: ...`を定義してから`ctypdef foo Foo`とする。
    2. 直接`struct Foo: ...`として定義する。

## 注３: ビルド引数の記述

標準ライブラリでないライブラリの場合、`setuptools`の`Extension`のオプションを明示的に指定しないと、コンパイルやリンクが通らないでしょう。

以下が[Cythonのドキュメンテーション](https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html)に書いてある雛形です：

```python
from setuptools import Extension, setup
from Cython.Build import cythonize

extensions = [
    # 第１引数がモジュール名、
    # 第２引数がモジュールに必要なコードファイル(.pyx, .c, ...)の配列
    Extension("primes", ["primes.pyx"],
        include_dirs=[...], # インクルードディレクトリの配列
        libraries=[...],    # ライブラリ名の配列（gccの'-l'オプションに渡す名前）
        library_dirs=[...]),# ライブラリディレクトリの配列（gccの'-L'オプション）

    # Everything but primes.pyx is included here.
    Extension("*", ["*.pyx"],
        include_dirs=[...],
        libraries=[...],
        library_dirs=[...]),
]
setup(
    name="My hello app",
    ext_modules=cythonize(extensions),
)
```
実際には`Extension`はレシピを書いたコンテナでしかありません。ビルド作業そのものは、`setup()`中に出てくる`cythonize`関数が行ないます。

他で書いた通り、`numpy.get_include()`あるいは`pkg-config <lib> --cflags`のようなコマンドを使うことで、ここの入力をある程度自動化することはできます。ユースケースによっては、最悪ユーザに環境変数を設定させて、ビルトスクリプトではそれを見に行く、という方式でもよいのかもしれない…。

# Python本来のもろもろのチェック機構を外す

Pythonには様々なチェック機構があり、これらの機構がPythonの柔軟性を生んでいます。ただ処理速度を追求する際には、これが問題になることがあります。

## 型チェック

Pythonでは型はあって無きが如しで、オブジェクトの属性アクセスはもっぱら`getattr()`と`call()`からできていると言ってもよいでしょう。`int`や`float`であってもこれは変わりません。できる限りCで定義された変数を使うべきです。

関数定義の際に引数の型を指定することで、`cdef`等価の型（Cネイティブ型、あるいは`cdef`クラス）であることを保障してコンパイルすることができます：

```cython

def pydef_add(int a, int b):
    return a + b

cdef int cdef_add(int a, int b):
    return a + b
```

たとえば`numpy`の場合も、Cの型情報をCython側で用意してくれている（？）のでそれを使うことができます。型情報の読み込みには`cimport`キーワードを使います:

```cython
cimport numpy as cnumpy

...

cdef cnumpy.ndarray arr # 配列ポインタの定義
cdef cnumpy.float64_t value = 0.0 # numpy.float64型の変数の定義
```

## Noneチェック

Pythonは暗黙的に、呼び出している名前が`None`を指しているかを毎回チェックしています。`@cython.nonecheck(False)`デコレータを使用することで、このチェックを切ることができます。ただ万が一ここに`None`が渡されるとプログラムがクラッシュするので注意が必要です。

これとは別に、「引数がNoneでない」ということを保障するために`not None`という修飾子があります：

```cython
def complicated(cnumpy.ndarray array not None):
   pass
```

## 配列チェック

Pythonは配列アクセスをする場合、自動的に添え字がオーバーフローしたりしないかをチェックしています。もし自分のコードで「オーバーフロー・アンダーフローが起こらない」ことが保障されるなら、`@cython.boundscheck(False)`デコレータを使用することでこのチェックを切ることができます。

このほかNumPyの配列を使う場合は、配列の形について一定の宣言をあらかじめすることができ、これによって処理を効率化することができます（コンパイル時にCythonがしてくれます）：

```cython
def calc_max(cnumpy.ndarray[cnumpy.float64_t, ndim=1] vec):
    ...
```
上の例のように、配列で用いているデータ型やデータの次元数を指定しておけます。ここにさらに上記`@cython.boundscheck(False)`をつけることで、より効率的な(Cネイティブに近い)コードになります


# GILに注意

## 感覚的なGILの理解

Pythonにはglobal interpreter lock (GIL)なる機構があり、Pythonの動作が遅くなる理由の半分くらいはこれだと思います。これは以下の原理に対する要請です：

> Pythonスレッドがいくらあろうと、ひとつのPythonプロセスにはPythonインタプリタはひとつしか存在しない。

これはある意味では、プロセス内でのPython環境の一意性を保つのには重要な機構です。それで、たくさんのスレッドがひとつのインタプリタにアクセスする際にしっちゃかめっちゃかにならないように、実行中のスレッドだけがインタプリタのロックを取得して、その他のスレッドはロックの順番待ちをしているわけです。これがGILです。

## Python環境での処理を減らす

そういうわけなので、Pythonのコードを実行する際には（たとえCythonコードからであっても）確実にGILが取得されます。たとえネイティブスレッドを使っていてもここでGILの取り合いがおこるので、処理速度は確実に落ちます。

なので、処理を早くするには「できるだけCネイティブの処理に置き換える(`cdef`等価なものだけを使う)」「Cネイティブ処理の間はGILを取得しない」ことが重要になります。Cython内では、`with nogil:`でコンテキストを始めることで、そのコンテキスト内ではGILなしで実行が行われます。逆に`with gil:`というコンテキスト指定もあります。

## GIL迷子にならない（コールバック関数には注意）

上記のポイントは逆に言うと、「Pythonオブジェクトを使うときにはいつでもGILが必要」ということです。実際問題、コールバック関数内からPythonオブジェクトを使いたい場合には注意が必要です。

ハードウェア関連のコールバック関数によっては、Pythonプロセスが生成したスレッドではないことがあります。そうするとコールバックの環境からインタプリタのアドレスがわからないので、その中でPythonオブジェクトを使おうとすると「GILが見つからない！」的なエラーが出てプログラムがクラッシュします。

こういう場合は、Pythonから見えるスレッド（Pythonスレッドでも、PyQtのスレッドでも）を自前で立ち上げて、そこで`Condition.wait()`などでコールバックの発生を待つしかなさそうです。


# 結語

Cythonは便利だけれど、裏で何が起こっているのか、プログラミングモデルを理解するのが大変です。機能自体はまだまだ（`ctypedef`とか`DEF`とか、Cヘッダファイルの読み込みとか）あるけれど、とりあえず基本的な部分の理解ができればよしとしましょう。