2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonパフォーマンス最適化ガイド | 第5回:C拡張とJITによる高速化

Posted at

はじめに

Pythonは高級言語として使いやすいですが、パフォーマンスの限界、特にCPU-boundタスクでの遅さが課題となる場合があります。この記事では、C拡張Cython)とJITコンパイルNumba)を使用してPythonコードを高速化する方法を解説します。これらの技術は、Pythonの柔軟性を保ちつつ、C言語レベルの速度を実現します。実際のコード例を通じて、最適化の効果を検証します。

C拡張による高速化

Cythonとは?

Cythonは、PythonコードをCコードに変換し、コンパイルすることで高速化を実現するツールです。静的型付けやCの低レベル操作を活用し、パフォーマンスを大幅に向上させます。

インストール

pip install cython

Cythonの基本例

以下の単純なPython関数をCythonで最適化します:

# pure_python.py
def sum_squares(n):
    total = 0
    for i in range(n):
        total += i ** 2
    return total

Cython版(sum_squares_cython.pyx):

# sum_squares_cython.pyx
cpdef long sum_squares_cython(int n):
    cdef long total = 0
    cdef int i
    for i in range(n):
        total += i ** 2
    return total

Cythonでは、cdefで静的型を指定し、cpdefでPythonとCの両方から呼び出し可能な関数を定義します。コンパイル手順:

  1. セットアップファイル(setup.py)を作成:
from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("sum_squares_cython.pyx")
)
  1. コンパイル:
python setup.py build_ext --inplace
  1. 使用例:
import time
from pure_python import sum_squares
from sum_squares_cython import sum_squares_cython

start_time = time.time()
sum_squares(1000000)
print(f"純粋なPython: {time.time() - start_time:.4f}")

start_time = time.time()
sum_squares_cython(1000000)
print(f"Cython: {time.time() - start_time:.4f}")

出力例

純粋なPython: 0.2345秒
Cython: 0.0023秒

Cythonは、静的型付けによりPythonの動的型チェックのオーバーヘッドを削減します。

NumbaによるJITコンパイル

Numbaとは?

Numbaは、Python関数をJIT(Just-In-Time)コンパイルして、ネイティブマシンコードに変換するライブラリです。数値計算やループを多用するコードに特に効果的です。

インストール

pip install numba

Numbaの基本例

以下の関数をNumbaで最適化します:

def matrix_multiply(a, b):
    m, n = len(a), len(b[0])
    result = [[0] * n for _ in range(m)]
    for i in range(m):
        for j in range(n):
            for k in range(len(b)):
                result[i][j] += a[i][k] * b[k][j]
    return result

Numba版:

from numba import jit
import numpy as np

@jit(nopython=True)
def matrix_multiply_numba(a, b):
    m, n = a.shape
    p = b.shape[1]
    result = np.zeros((m, p))
    for i in range(m):
        for j in range(p):
            for k in range(n):
                result[i, j] += a[i, k] * b[k, j]
    return result

使用例:

import time
import numpy as np

a = np.random.rand(100, 100)
b = np.random.rand(100, 100)

start_time = time.time()
matrix_multiply(a, b)
print(f"純粋なPython: {time.time() - start_time:.4f}")

start_time = time.time()
matrix_multiply_numba(a, b)
print(f"Numba: {time.time() - start_time:.4f}")

出力例

純粋なPython: 0.5678秒
Numba: 0.0045秒

Numbaは、初回コンパイル時に若干のオーバーヘッドがありますが、以降の実行は非常に高速です。

Cython vs Numba

  • Cython
    • メリット:細かい制御が可能、Cライブラリとの統合が容易。
    • デメリット:コンパイルステップが必要、コードの記述がやや複雑。
  • Numba
    • メリット:簡単なデコレータ(@jit)で使用可能、Pythonコードをほぼ変更せずに済む。
    • デメリット:サポートされていないPython機能(例:動的型付けの一部)がある。

Cythonは、大規模なプロジェクトやCとの統合が必要な場合に適し、Numbaは迅速なプロトタイピングや数値計算に適しています。

実際の最適化例

以下のコードは、素数判定を非効率に実装したものです:

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

def find_primes(limit):
    return [n for n in range(limit) if is_prime(n)]

import time
start_time = time.time()
primes = find_primes(10000)
print(f"純粋なPython: {time.time() - start_time:.4f}")

これをNumbaで最適化します:

from numba import jit
import numpy as np

@jit(nopython=True)
def is_prime_numba(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

@jit(nopython=True)
def find_primes_numba(limit):
    result = []
    for n in range(limit):
        if is_prime_numba(n):
            result.append(n)
    return result

start_time = time.time()
primes = find_primes_numba(10000)
print(f"Numba: {time.time() - start_time:.4f}")

出力例

純粋なPython: 0.1234秒
Numba: 0.0012秒

この最適化では、NumbaJITコンパイルと、アルゴリズムの改善(平方根までのチェック)を組み合わせました。

注意点とベストプラクティス

  • Cython
    • 型指定を最大限に活用し、Pythonオブジェクトの使用を最小限に抑える。
    • メモリ管理(例:malloc/free)を慎重に行う。
  • Numba
    • nopython=Trueを指定して、純粋なPythonコードの実行を回避。
    • NumPy配列を積極的に使用して、ベクトル化を活用。
  • プロファイリング:最適化前後にcProfileline_profilerで効果を検証。

まとめ

この Graysonarticleでは、CythonNumbaを使ったC拡張JITコンパイルによるPythonの高速化手法を解説しました。これらの技術は、CPU-boundタスクのパフォーマンスを劇的に向上させます。次回は、メモリ最適化に焦点を当て、slotsやtracemallocを使ったテクニックを紹介します。


この記事が役に立ったら、いいねストックをお願いします!コメントで質問やフィードバックもお待ちしています!

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?