- numba.jitによる高速化のクラス編です。公式にもほとんど情報が無いので結構苦労しました。
※2024/2/25追記:以下も分かりやすいです。
- numba.jitを知らない方はまず関連記事を読んだほうがスムーズだと思います。
関連記事
computational-sediment-hyd.hatenablog.jp
computational-sediment-hyd.hatenablog.jp
過去記事 と同様に半径1の円を簡素化した64角形の面積を求める問題を例とします。
computational-sediment-hyd.hatenablog.jp
from shapely.geometry import Point import numpy as np
p = Point(0.0, 0.0) x = p.buffer(1.0) s = x.simplify(0.001, preserve_topology=False) polygon = np.array( [ p for p in s.exterior.coords]) len(polygon) # 65
s
def Area(p): s = np.array( [ p[i-1][0]*p[i][1] - p[i][0]*p[i-1][1] for i in range(1, len(p)) ] ) return 0.5*np.abs(np.sum(s)) Area(np.array(polygon)) # 3.1365484905459393
numba version
numbaのバージョンを確認する。この手の話はまだまだ仕様変更があると思われます。
import numba
numba.__version__
'0.46.0'
PCのスペック
一応速度比較を行っているので。
ノートPCのスペック : VAIO Pro PG - 趣味で計算流砂水理 Computational Sediment Hydraulics for Fun Learning
単純なクラスの高速化
- 面積を求めるメソッドAreaを持つクラスshapeを定義する。
- クラス変数の型を指定して、次のようにクラスの前に@jitclass(クラス変数の型)をつける。
from numba import jitclass from numba import float64 spec = [ ('polygon', float64[:,:]) ] @jitclass(spec) class shape(object): def __init__(self, polygon): self.polygon = polygon def Area(self): p = self.polygon s = np.array( [ p[i-1][0]*p[i][1] - p[i][0]*p[i-1][1] for i in range(1, len(p)) ] ) return 0.5*np.abs(np.sum(s))
%%timeit c = shape(polygon) c.Area()
- 参考サイト
Compiling Python classes with @jitclass — Numba 0.50.1 documentation
速度比較:%%timeで計測
numbaにより約10倍高速化する。
numbaあり
13.2 µs ± 229 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
numbaなし
119 µs ± 614 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
ネストされたクラスの高速化
- 先程のクラスshape内に新たなクラスmodelを設ける。
- クラス変数にクラスを与える場合、型はxxxx.class_type.instance_typeと指定する。
- 参考サイトでは、deferred_typeが必要と書いてあるが無くても問題無さそう。
from numba import jitclass from numba import float64 spec = [ ('polygon', float64[:,:]) ] @jitclass(spec) class model(object): def __init__(self, polygon): self.polygon = polygon def Area(self): p = self.polygon s = np.array( [ p[i-1][0]*p[i][1] - p[i][0]*p[i-1][1] for i in range(1, len(p)) ] ) return 0.5*np.abs(np.sum(s)) spec = [ ('model', model.class_type.instance_type) ] @jitclass(spec) class shape(object): def __init__(self, polygon): self.model = model(polygon) def Area(self): return self.model.Area()
%%timeit c = shape(polygon) c.Area()
- 参考サイト
python - How to nest numba jitclass - Stack Overflow
速度比較:%%timeで計測
numbaにより約10倍高速化する。
numbaあり
13.6 µs ± 169 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
numbaなし
122 µs ± 4.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
クラスのリストを含むクラスの高速化
- クラスshape内のpolygonをクラスpointのリストとして定義する。
- クラス変数にクラスのリストを与える場合、types.List(xxx.class_type.instance_type, reflected=True)と指定する。
- reflected=Trueは必須。(詳細は理解していません。詳しい人教えて下さい。)
from numba import jitclass from numba import float64 from numba import types spec = [ ('coordinate', float64[:]) ] @jitclass(spec) class point(object): def __init__(self, p): self.coordinate = p spec = [ ('polygon', types.List(point.class_type.instance_type, reflected=True)) ] @jitclass(spec) class shape(object): def __init__(self, polygon): self.polygon = [point(polygon[i]) for i in range(len(polygon))] def Area(self): p = self.polygon s = np.array( [ p[i-1].coordinate[0]*p[i].coordinate[1] - p[i].coordinate[0]*p[i-1].coordinate[1] for i in range(1, len(p)) ] ) return 0.5*np.abs(np.sum(s))
%%timeit c = shape(polygon) c.Area()
- 参考サイト
https://groups.google.com/a/continuum.io/forum/#!topic/numba-users/MygFRmKlr24
速度比較:%%timeで計測
numbaにより約6倍高速化する。遅い書き方をしているが、numbaなしの場合あまり遅くならない。numbaありの場合は上の2ケースより遅くなる。
numbaあり
22 µs ± 519 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
numbaなし
115 µs ± 2.58 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
調査中:継承
継承された側のクラスのみにnumbaをつける。
super()メソッドが使えないっぽい。オーバーライドは問題なく使える。
Gist
大きなモデルを作る場合はクラスは必須なのでnumbaが使えるとpythonの可能性が広がります。