mei_13のPython講座 ロゴ

【解説】Pythonの実行速度を劇的に向上させる「Numba」の世界




Pythonの実行速度を劇的に向上させる「Numba」の世界


Hirokiのアイコン
【Hiroki】 Yukiさん、最近Pythonで数値計算のプログラムを書いているんですけど、計算量が増えるとどうしても実行速度が遅くなってしまって……。Pythonは書きやすくて大好きなんですけど、速度面で少し悩んでいます。何かいい解決策はないでしょうか?


Yukiのアイコン
【Yuki】 Pythonは直感的に書ける素晴らしい言語ですけれど、実行速度については課題になることが多いですよね……。特にループ処理が重なると、どうしても時間がかかってしまう傾向があります。そんなとき、わたしがおすすめしたいのが「Numba」というライブラリです。


Hirokiのアイコン
【Hiroki】 Numba……ですか? 名前は聞いたことがあるような気がします。それは、どういったライブラリなんですか?


Yukiのアイコン
【Yuki】 Numbaは、PythonのコードをJIT(Just-In-Time)コンパイルして、機械語に変換してくれるライブラリです。簡単に言うと、Pythonのコードを実行する直前に、コンピュータが理解しやすい形に高速化してくれる魔法のようなツール……かもしれません。特に、数値計算やループ処理において、C言語やFortranに近い速度を引き出すことができるんですよ。


Hirokiのアイコン
【Hiroki】 C言語に近い速度! それはすごいですね。でも、コンパイルって難しそうなイメージがあります。特別な設定が必要なんじゃないですか?


Yukiのアイコン
【Yuki】 そう思いますよね。でも、Numbaの使い方は驚くほどシンプルなんです。基本的には、高速化したい関数の上に「デコレータ」を一行書き加えるだけです。……少しだけ、具体的な仕組みからお話ししても大丈夫でしょうか?


Hirokiのアイコン
【Hiroki】 はい、ぜひお願いします!

Numbaとは何か?:JITコンパイルの仕組み


Yukiのアイコン
【Yuki】 通常、Pythonは一行ずつコードを解釈して実行する「インタプリタ」という方式をとっています。これが柔軟性の秘訣なのですが、どうしてもオーバーヘッドが生じてしまいます。


Yukiのアイコン
【Yuki】 Numbaは、LLVM(Low Level Virtual Machine)というコンパイラ基盤を利用しています。関数が呼び出された瞬間に、その関数の引数の型を解析して、その型に最適化されたマシンコードを生成します。これを「JITコンパイル」と呼びます。一度コンパイルされた内容はメモリにキャッシュされるので、二回目以降の呼び出しは非常に高速になるんです。


Hirokiのアイコン
【Hiroki】 なるほど、実行する時にその場で最適化してくれるんですね。


Yukiのアイコン
【Yuki】 ええ、その通りです。ただ、最初に呼び出すときだけはコンパイルの時間が必要になるので、少しだけ待つことになるかもしれません。夜の静かな時間にプログラムを動かしていると、そのわずかな待ち時間も少し気になってしまうかもしれませんが……二回目からは驚くほどスムーズに動きますよ。

基本的な使い方:@jitデコレータを添えるだけ


Hirokiのアイコン
【Hiroki】 実際にどうやって使うのか、コードを見てみたいです!


Yukiのアイコン
【Yuki】 わかりました。一番シンプルな例を紹介しますね。まずはライブラリをインストールする必要があります。

pip install numba


Yukiのアイコン
【Yuki】 そして、次のようにコードを書きます。

from numba import jit
import numpy as np

@jit
def calculate_sum(n):
    result = 0
    for i in range(n):
        result += i
    return result

# 初回の呼び出し(ここでコンパイルが行われます)
print(calculate_sum(10000000))


Hirokiのアイコン
【Hiroki】 えっ、これだけですか? @jit と書くだけでいいなんて……。


Yukiのアイコン
【Yuki】 はい、これだけでNumbaはこの関数を解析して高速化しようと試みてくれます。ただ、より確実に、そして安全に高速化するためには、もう少しだけコツがあるんです。

nopythonモードの重要性:最速を目指すために


Hirokiのアイコン
【Hiroki】 コツ、ですか? もっと速くなる方法があるんでしょうか。


Yukiのアイコン
【Yuki】 はい。実は @jit だけだと、Numbaがうまく高速化できない部分を見つけたときに、こっそり「オブジェクトモード」という、通常のPythonに近い遅いモードに切り替えてしまうことがあるんです。それを防ぐために、nopython=True というオプションを指定するのが、現在のNumbaの推奨される使い方です。


Hirokiのアイコン
【Hiroki】 nopython=True……。Pythonを使わない、という意味ですか?


Yukiのアイコン
【Yuki】 正確には、「Pythonのランタイム(CAPI)を介さずに、完全にマシンコードだけで実行する」という意味です。これが一番速いモードになります。最近では、これを短縮した @njit というデコレータを使うのが一般的ですね。

from numba import njit

@njit
def fast_function(a, b):
    return a + b


Hirokiのアイコン
【Hiroki】 @njit のほうが短くて書きやすいですね。これからはこっちを使ってみます。


Yukiのアイコン
【Yuki】 いい判断だと思います……。もし、このモードでエラーが出る場合は、そのコードの中にNumbaがサポートしていないPythonの機能(例えば、複雑なクラスや特定の外部ライブラリなど)が含まれているということになります。エラーメッセージは少し不愛想に見えるかもしれませんが、どこを修正すればいいか教えてくれる大切な手がかりになります。

NumbaとNumPyの相性:ベクトル演算の加速


Hirokiのアイコン
【Hiroki】 僕はNumPyもよく使うんですけど、NumPyと一緒に使っても大丈夫ですか?


Yukiのアイコン
【Yuki】 もちろんです。むしろ、NumbaはNumPyと非常に相性がいいんですよ。NumPy自体も十分に高速ですが、複数のNumPy操作を組み合わせる場合、一時的な配列がメモリ上に作成されてしまうことがあります。Numbaを使うと、それらの操作を一つにまとめて最適化してくれるので、さらにパフォーマンスが向上することがあります。


Hirokiのアイコン
【Hiroki】 例えば、どんな風に書くのがいいんでしょうか。


Yukiのアイコン
【Yuki】 例えば、配列の各要素に対して複雑な計算を行う場合ですね。

import numpy as np
from numba import njit

@njit
def analyze_data(arr):
    # NumPyの配列をそのまま扱えます
    res = np.empty_like(arr)
    for i in range(len(arr)):
        # 複雑な条件分岐や計算も、ループの中で高速に処理できます
        if arr[i] > 0:
            res[i] = np.sqrt(arr[i])
        else:
            res[i] = arr[i] ** 2
    return res

data = np.random.randn(1000000)
result = analyze_data(data)


Hirokiのアイコン
【Hiroki】 NumPyの関数をループの中で使っていますね。普通、Pythonのループでこれをやるとすごく遅くなるって教わりましたけど、Numbaなら大丈夫なんですね。


Yukiのアイコン
【Yuki】 はい、Numbaならループが全く怖くなくなります。むしろ、無理に難解なユニバーサル関数(ufunc)を組み合わせるよりも、素直にループで書いたほうが読みやすくて速い、なんてこともあるくらいです。

並列処理(Parallel)の活用


Hirokiのアイコン
【Hiroki】 もっと欲張ってもいいですか? 最近のCPUはコアがたくさんありますよね。それらを全部使って計算を速くすることはできますか?


Yukiのアイコン
【Yuki】 ふふ、Hirokiくんは意欲的ですね。はい、Numbaならそれも簡単にできます。parallel=True という引数と、prange という特別な関数を使います。


Hirokiのアイコン
【Hiroki】 prangerange じゃないんですか?


Yukiのアイコン
【Yuki】 はい。parallel range の略で、このループは並列化していいですよ、とNumbaに教えるためのものです。

from numba import njit, prange
import numpy as np

@njit(parallel=True)
def parallel_sum(arr):
    s = 0
    # prangeを使うことで、複数のCPUコアで分割して計算します
    for i in prange(len(arr)):
        s += arr[i]
    return s

data = np.ones(100_000_000)
print(parallel_sum(data))


Hirokiのアイコン
【Hiroki】 これだけでマルチコアを活用した並列計算ができるなんて……。自分でスレッドを管理したりするのは大変そうですけど、これなら僕にもできそうです。


Yukiのアイコン
【Yuki】 ええ、難しい同期処理などはNumbaが裏側でやってくれます。……ただし、計算内容があまりに単純すぎると、並列化の準備にかかる時間のほうが長くなって、逆に遅くなってしまうこともあるので注意してくださいね。何事もバランスが大切……だと思います。

注意点と制限:何でも速くなるわけではない


Hirokiのアイコン
【Hiroki】 ここまで聞いていると、全部の関数に @njit をつけたくなっちゃいますね。


Yukiのアイコン
【Yuki】 あわわ、それは少し待ってください……。Numbaにも苦手なことはたくさんあるんです。


Hirokiのアイコン
【Hiroki】 苦手なこと、ですか?


Yukiのアイコン
【Yuki】 はい。まず、Numbaは数値計算に特化しています。文字列の複雑な操作や、辞書(dict)やリスト(list)といったPython特有の動的なデータ構造を多用する処理は、あまり得意ではありません。また、PandasのDataFrameをそのままNumbaの関数の中で扱うことも、基本的にはできません。


Hirokiのアイコン
【Hiroki】 そうなんですね。なんでもかんでも速くしてくれるわけじゃないんだ。


Yukiのアイコン
【Yuki】 そうなんです。基本的には「NumPyの配列」と「基本的な数値型(int, float)」、そして「ループ」を中心に構成されている関数で真価を発揮します。


Yukiのアイコン
【Yuki】 あと、デバッグが少し難しくなることもあります。コンパイルされた後のコードは中身が見えにくいですから……。最初は @njit をつけずにデバッグして、正しく動くことを確認してから、仕上げに @njit を添える……という手順が、わたしのおすすめです。


Hirokiのアイコン
【Hiroki】 なるほど。まずは普通に作って、ボトルネックになっている重い計算部分だけをNumbaに任せる、という使い方が良さそうですね。


Yukiのアイコン
【Yuki】 その通りです。そういった「適材適所」の考え方は、プログラミングにおいてとても大切だと思います。

まとめ


Hirokiのアイコン
【Hiroki】 今日はNumbaについて教えてくれてありがとうございました! Pythonの書きやすさを維持したまま、これほどまでの速度が出せるなんて驚きでした。


Yukiのアイコン
【Yuki】 お役に立てたなら嬉しいです。Numbaを使いこなせるようになると、Pythonでできることの幅がぐっと広がりますよ。データ分析や科学シミュレーションなど、重い処理が必要になったときは、ぜひ思い出してあげてください。


Yukiのアイコン
【Yuki】 もし、もっと詳しく知りたくなったら、公式サイトのドキュメントも覗いてみてくださいね。英語ですけれど、コード例が豊富で、眺めているだけでも勉強になるかもしれません。

Numba Documentation Numba User Guide


Hirokiのアイコン
【Hiroki】 はい、見てみます! 早速自分のコードを書き換えてみますね。


Yukiのアイコン
【Yuki】 頑張ってください。……あ、でも、あまり夜更かししすぎないように気をつけてくださいね。プログラムが速くなって時間が余ったら、ゆっくりお茶でも飲んで休むのもいいと思います……。


Hirokiのアイコン
【Hiroki】 はい、ありがとうございます、Yukiさん!

Numba活用のポイント再確認


Yukiのアイコン
【Yuki】 最後に、大切なポイントをもう一度まとめておきますね。

  • @njit(または @jit(nopython=True))を使うのが基本
    • Pythonのランタイムを介さない、最速のモードです。
  • 数値計算とループが最大の得意分野
    • NumPy配列との組み合わせで真価を発揮します。
  • 初回の呼び出しにはコンパイル時間がかかる
    • 実行速度を測るときは、2回目以降の時間を計るようにしましょう。
  • 並列化には parallel=Trueprange
    • CPUの性能をフルに引き出せますが、計算規模が大きい場合に有効です。
  • 型推論を意識する
    • 同じ関数に違う型の引数(整数と浮動小数点など)を渡すと、そのたびに新しいマシンコードが生成されます。


Hirokiのアイコン
【Hiroki】 わかりました! これで自信を持って数値計算に取り組めそうです。


Yukiのアイコン
【Yuki】 ……はい。Hirokiくんのプログラムが、風のように軽やかに動くことを願っています。



< SymPy
コラム一覧に戻る
Dask >

この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。

「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。

  • 専門家の知見に基づいた、保守性の高いコード設計
  • AIの専門家による、Gemini API等の最新AIを組み合わせた高度な自動化
  • ChatGPT等が生成したコードのデバッグ・最適化

「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。

➡ ココナラで制作・相談を依頼する(見積もり無料)


初心者から始められるPythonレッスン

プログラミング未経験者・初心者歓迎!
月額4,000円で質問し放題!!
● 完全オンライン
● 翌日までには必ず返信
● 挫折しない独自の学習メソッド
● 圧倒的高評価!!
テキストベースで時間を選ばない
● 高品質なサンプルコード
詳細はこちら
興味がある方はまず質問だけでもどうぞ!



AIアシスタント Yuki