Python PR

【Python】関数の結果をキャッシュして高速化する

記事内に商品プロモーションを含む場合があります

この記事では、関数をキャッシュして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]で結果を返しています。

まとめ

この記事では、関数をキャッシュして高速化する方法を解説しました。

関数はキャッシュしておくことでめちゃくちゃ高速化することができます。少しだけ制限はありますが、よく使う関数や重い関数はキャッシュしておきましょう!

それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ