アーリーバインディングによる高速化

動的言語としての性質から、 Python では、クラスやオブジェクトをクラス階 層中のどこに位置づけるか、よりも、メソッドやアトリビュートをどうするか、 で考えるプログラミングスタイルをとりやすくなっています。

そため、 Python の文法はとてもゆるく、快適かつ迅速に開発を行えます。た だし、その代償として、データ型の管理という「お役所仕事」はインタプリタ に課されています。実行時、インタプリタは名前空間の検索やアトリビュート の取得、引数やキーワード引数のタプルの解析など、様々な仕事を行います。 この実行時の「レイトバインディング」こそ、 C++ のような「アーリーバイ ンディング」型言語に対して Python が低速な主要因です。

一方、 Cython では、アーリーバインディングのプログラミングテクニックを 使って、大幅に速度を向上させられます。

例えば、以下のような (取るに足らない) コードについて考えましょう:

cdef class Rectangle:
    cdef int x0, y0
    cdef int x1, y1
    def __init__(self, int x0, int y0, int x1, int y1):
        self.x0 = x0; self.y0 = y0; self.x1 = x1; self.y1 = y1
    def area(self):
        area = (self.x1 - self.x0) * (self.y1 - self.y0)
        if area < 0:
            area = -area
        return area

def rectArea(x0, y0, x1, y1):
    rect = Rectangle(x0, y0, x1, y1)
    return rect.area()

rectArea() メソッドでは、 rect.area() の呼び出しや、 area() メソッドの処理で大きな Python のオーバヘッドを生じます。

ところが、 Cython では、このオーバヘッドの大半を、 Cython のコード内で 呼び出しを行えるものについてそぎ落とすことができます。例えば:

cdef class Rectangle:
    cdef int x0, y0
    cdef int x1, y1
    def __init__(self, int x0, int y0, int x1, int y1):
        self.x0 = x0; self.y0 = y0; self.x1 = x1; self.y1 = y1
    cdef int _area(self):
        cdef int area
        area = (self.x1 - self.x0) * (self.y1 - self.y0)
        if area < 0:
            area = -area
        return area
    def area(self):
        return self._area()

def rectArea(x0, y0, x1, y1):
    cdef Rectangle rect
    rect = Rectangle(x0, y0, x1, y1)
    return rect._area()

この Rectangle 拡張クラスには、面積を計算する二つのメソッドがあります。 一つは C で書かれた効率的なメソッド _area() で、もう一つは Python から呼び出せる area() です。後者は、前者の薄いラッパとし て働きます。また rectArea() 関数の中でも、ローカル変数 rect を Rectangle 型と宣言することで、アーリーバインディングを行なっていま す。 rect を動的に代入する代わりに、上のような宣言を使えば、より効 率的な C の _rect() メソッドに直接アクセスできるのです。

ただし、 Cython には、さらに簡単な方法があります。デュアルアクセスのメ ソッドを宣言するやり方です - デュアルアクセスメソッドとは、 C レベルで は効率的な呼び出しを可能としながら、Python の関数呼び出しのオーバヘッ ドと引き換えに、 pure Python コードでのアクセスが可能なメソッドです。 以下のようなコードを考えてみましょう:

cdef class Rectangle:
    cdef int x0, y0
    cdef int x1, y1
    def __init__(self, int x0, int y0, int x1, int y1):
        self.x0 = x0; self.y0 = y0; self.x1 = x1; self.y1 = y1
    cpdef int area(self):
        cdef int area
        area = (self.x1 - self.x0) * (self.y1 - self.y0)
        if area < 0:
            area = -area
        return area

def rectArea(x0, y0, x1, y1):
    cdef Rectangle rect
    rect = Rectangle(x0, y0, x1, y1)
    return rect.area()

Note

Cython の以前のバージョンでは、 cpdef キーワードは rdef という名前でした - とはいえ、効果は同じです。

このコードでは、 area() メソッドは一つだけで、 cpdef を使って定義されています。 cpdef は C の関数として効率的な 呼び出しが可能な一方、 pure Python のコードから (または、Cython のコー ドからレイトバインディングで) アクセスできます。

Cython のコード内では、変数はすでに「アーリーバインドされて」いるので (Rectangle 型として宣言されていたり、キャストされているので) 変数に対 して area メソッドを呼び出すと、効率的な C のコードパスを使い、 Python のオーバヘッドをスキップします。

Python や Pyrex のコード内では、Rectangle オブジェクトの保持に通常のオ ブジェクト変数を使っているので、 area メソッドの呼び出しには以下のオー バヘッドが生じます:

  • area メソッドを解決するために、アトリビュートをルックアップする
  • 引数と、キーワード辞書のタプルをパッキングする (ただし、上の例では空)
  • Python API を使ってメソッドを呼び出す

area メソッド自体では、以下のようなオーバヘッドが生じるでしょう:

  • タプルとキーワードのパージング
  • 計算コードの実行
  • 計算結果を Python のオブジェクトに変換して返す

Cython の中では、変数の宣言やキャストによって強い型付けをすることで、 かなりの最適化を図れます。メソッド呼び出しを何度も行うタイトなループで、 メソッドの中身が pure C で書かれている場合には、その違いは莫大になりま す。

Previous topic

ソースファイルとコンパイル

Next topic

Cython から C++ を使う

This Page