趣味で計算流砂水理

Computational Sediment Hydraulics for Fun

numba.jitによる高速化:クラス編

  • numba.jitによる高速化のクラス編です。公式にもほとんど情報が無いので結構苦労しました。
  • numba.jitを知らない方はまず関連記事を読んだほうがスムーズだと思います。

関連記事

computational-sediment-hyd.hatenablog.jp

computational-sediment-hyd.hatenablog.jp

過去記事 と同様に半径1の円を簡素化した64角形の面積を求める問題を例とします。

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

f:id:SedimentHydraulics:20200112134907p:plain

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 - 趣味で計算流砂水理

単純なクラスの高速化

  • 面積を求めるメソッド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)

Gist

Jupyter Notebook Viewer


大きなモデルを作る場合はクラスは必須なのでnumbaが使えるとpythonの可能性が広がります。