σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 10 · 高性能Pythonエンジニアリング›トピック 10.7·~1 時間上級

Cython

ロードマップ上の位置モジュール 10/11 · トピック 7/13
10.7 Cython
—ラボスコア— / 100合計XP—連続日数—

C の型を持つ Python をコンパイルする

Cython は、Python にオプションの C 型宣言を加えた言語のコンパイラです。.pyx ファイルを書くと、Cython がそれを C に翻訳し、C コンパイラが拡張モジュールにして、他のモジュールと同じように import できます。素の Python コードをこうしてコンパイルするだけでも少し速くなりますが、本当の効果は型を宣言したときに出ます。cdef double total は、total が Python オブジェクトではなくマシンの数値だと Cython に伝えるので、ループはインタプリタのオーバーヘッドなしに生の数値で動きます。重い数値ループは Numba と同様に50〜100倍速くなりえますが、配布できる普通のコンパイル済みモジュールが得られます。

# What a Cython file looks like: Python with cdef type declarations (shown here as text)
pyx = """\
cpdef double dot(double[:] a, double[:] b):
    cdef double total = 0.0
    cdef long i
    for i in range(a.shape[0]):
        total += a[i] * b[i]
    return total
"""
print(pyx.splitlines()[0])                           # cpdef double dot(double[:] a, double[:] b):
print(sum("cdef" in line for line in pyx.splitlines()))     # 2   two typed declarations inside the function

型の宣言:cdef、cpdef、メモリビュー

cdef は C の変数(cdef double total、cdef long i)、または C のコードからしか呼べない関数を宣言します。cpdef は Python からも C からも呼べる関数を宣言し、モジュールの入口に向いています。def は通常の Python 関数のままです。配列は型付きメモリビューで渡します。1次元の double 配列なら double[:] で、NumPy 配列をコピーなしで受け取り、C の速度でインデックスできます。cpdef double mean(double[:] x, long n) のようなシグネチャは、Python の動的な型を正確な型に置き換えます。型を省略したり object と宣言したりすると、遅い Python オブジェクトのままです。

# Building the declarations as text: one table from Python type names to C types
C_TYPES = {"int": "long", "float": "double", "bool": "bint", "array[float]": "double[:]"}

def c_type(py_type):
    return C_TYPES.get(py_type, "object")

def cdef_signature(name, params, returns):
    result = "void" if returns is None else c_type(returns)
    arguments = ", ".join(f"{c_type(t)} {p}" for p, t in params)
    return f"cpdef {result} {name}({arguments})"

print(cdef_signature("mean", [("x", "array[float]"), ("n", "int")], "float"))    # cpdef double mean(double[:] x, long n)
print(cdef_signature("log_event", [("message", "dict")], None))                  # cpdef void log_event(object message)

モジュールのビルドと、型を付ける場所の見つけ方

cythonize(["pricing.pyx"]) を呼ぶ setup.py は、setuptools にファイルの翻訳とコンパイルを指示します。python setup.py build_ext --inplace でコードの隣に共有ライブラリができ、import pricing で読み込まれます。手軽な試用には、import 時にコンパイルする pyximport があります。まだ遅い箇所を知るには cython -a pricing.pyx を実行します。各行が使っている Python の仕組みの量に応じて黄色で塗られた HTML ページが作られ、白い行は純粋な C です。ホットなループ内の黄色い行の変数に型を付け、不要だと確認したチェックを @cython.boundscheck(False) と @cython.wraparound(False) で無効にすると、インデックス参照が C 並みに安くなります。

# The build script is a few lines of text; this generates it for a list of modules
def build_setup(modules):
    files = ", ".join(f'"{module}.pyx"' for module in modules)
    return "from setuptools import setup\nfrom Cython.Build import cythonize\n\n" + f"setup(ext_modules=cythonize([{files}]))\n"

print(build_setup(["pricing", "risk"]), end="")
# from setuptools import setup
# from Cython.Build import cythonize
#
# setup(ext_modules=cythonize(["pricing.pyx", "risk.pyx"]))
# The loop a .pyx file would hold, as plain Python: same result, compiled once types are added
def dot(a, b):
    total = 0.0
    for i in range(len(a)):
        total += a[i] * b[i]
    return total

prices = [184.25, 410.5, 99.0]
quantities = [100.0, 50.0, 200.0]
print(dot(prices, quantities))                       # 58750.0  the compiled version returns exactly this number
print(dot(prices, quantities) == sum(p * q for p, q in zip(prices, quantities)))     # True
Cython か Numba か、どちらでもないかNumba は軽い選択肢で、デコレータ1つ、ビルド手順なしで、数値関数に最適です。Cython はビルド手順と2種類目のファイルが必要ですが、JIT の起動なしで配布できる通常のモジュールが得られ、C ライブラリの呼び出しや C と Python のコードの自由な混在もできます。まず NumPy のベクトル化、次に Numba、最後に Cython の順に試し、どの場合も信頼する前に出力を素の Python 版と比べましょう。
  • Cython は .pyx ファイル(Python に任意の C 型を加えたもの)を C、さらに拡張モジュールにコンパイルし、普通に import できる
  • cdef は C の変数と C 専用の関数を、cpdef は Python と C の両方から呼べる関数を宣言する。配列は double[:] のような型付きメモリビュー
  • setup.py の cythonize でビルドし、cython -a でホットループ内の黄色(遅い)行を探し、素の Python の結果と照合する

次は pybind11 による C++ の呼び出しです。既存の C++ コードを Python に公開します。

ラボ:採点される内容

  • c_type で Python の型を C の型に対応づける25点
  • cdef_signature で関数ヘッダーを書く25点
  • declare_locals でローカル変数に型を付ける25点
  • build_setup でビルドスクリプトを書く25点
エクスプローラー · lab-123-1
▾ lab-123-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-123-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-123-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける