この記事では、関数をキャッシュして2度目以降の呼び出しを高速化する方法を解説します。
頻繁に呼び出す関数やコストのかかる関数などはキャッシュしておくことで高速化することができます。しかし、キャッシュに向かない関数もあるのでしっかり仕組みを理解して使いましょう。
それでは、関数のキャッシュ方法を見ていきましょう!
関数をキャッシュするには、Python 3.9で追加されたfunctoolsモジュールのcacheデコレータを使うことでめちゃくちゃ簡単に実装できます。
functoolsモジュールは標準ライブラリです。
それでは、実際にキャッシュした関数を使ってみましょう!
import time # 関数の実行時間を測るデコレータ def tictoc(func): def _wrapper(*args, **keywargs): start_time = time.time() result = func(*args, **keywargs) print('time: {:.9f} [sec]'.format(time.time() - start_time)) return result return _wrapper # ここからが本題 from functools import cache @tictoc @cache # デコレータを付与 def func(num): # なんらかの重い処理 for _ in range(1000000): num += 1 return num # 1度目の呼び出し func(1) # 2度目の呼び出し func(1)
実行結果
time: 0.117470980 [sec] time: 0.000002146 [sec]
2度目の呼び出しがめちゃくちゃ早くなったのがわかります!
キャッシュには、辞書が使われています。関数に渡された引数がkeyとして、関数の戻り値をvalueとして保存します。
そして、関数を呼び出した際に渡された引数が辞書のkeyとして存在する場合、関数を処理せずに辞書からkey(引数)に対応するvalue(戻り値)を返します。
注意として辞書を使用するので引数はハッシュ可能な値でなければならず、キーワード引数の順序が異なる場合は、異なるキャッシュとして保存されます。
例えば、f(a=1, b=2)とf(b=2, a=1)は別。
あとで辞書を使ってキャッシュを実装しているので実際の仕組みはその時に確認してください。
同じ引数を指定しても同じ値を返さない関数をキャッシュしてしまうと、2回目以降に同じ値を返すようになってしまい、その関数の本来の結果を受け取ることができなくなってしまいます。
なので、関数内でrandom()やtime()などを使っている場合は注意しましょう❗️
from random import randint from functools import cache @cache def rand(): return randint(1, 10) print(rand()) print(rand()) print(rand()) print(rand())
実行結果
3 3 3 3
上記のように全て最初に返された結果になってしまう。
cacheデコレータは、Python 3.9からしか使えないので自分で実装する方法を考えてみましょう。辞書を使うことで意外と簡単に実装することができます。
import time # 実行時間を計測 def tictoc(func): def _wrapper(*args, **keywargs): start_time = time.time() result = func(*args, **keywargs) print('time: {:.9f} [sec]'.format(time.time() - start_time)) return result return _wrapper # キャッシュデコレータ def cache(func): c = {} def _wrapper(*args): if args not in c: c[args] = func(*args) return c[args] return _wrapper @tictoc @cache def func(num): # なんらかの重い処理 for _ in range(1000000): num += 1 return num func(1) func(1) func(2) func(2)
実行結果
time: 0.092554092 [sec] time: 0.000005245 [sec] time: 0.088145971 [sec] time: 0.000007153 [sec]
辞書に引数の値のkeyが存在しなければ辞書[args] = func(*args)を格納します。returnには、辞書[args]で結果を返しています。
この記事では、関数をキャッシュして高速化する方法を解説しました。
関数はキャッシュしておくことでめちゃくちゃ高速化することができます。少しだけ制限はありますが、よく使う関数や重い関数はキャッシュしておきましょう!
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ