そんなリザルトキャッシュで大丈夫か? #jjug
DESCRIPTION
JJUGナイトセミナー2014年9月のセッション 「そんなリザルトキャッシュで大丈夫か?」 のスライドです。TRANSCRIPT
そんなリザルトキャッシュ で大丈夫か?@making 槙 俊明
2014-09-17
ここでいうリザルトキャッシュとは
•重い処理の結果を格納するメモリ(キャッシュ)
•リザルトキャッシュを再利用することで性能向上が期待できる
あなたのリザルトキャッシュ
•スレッドセーフですか?
•スケーラブルですか?
5-6 「効率的でスケーラブルなリザルトキャッシュを
構築する」 の内容をご紹介します
たまにみる実装
たまにみる実装static Map<BigInteger, List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … }
たまにみる実装static Map<BigInteger, List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … }
たまにみる実装static Map<BigInteger, List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … } キャッシュになかったら 計算してキャッシュに追加
😡
static Map<BigInteger, List<BigInteger>> cache = new HashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解 cache.put(n, result); } // … } スレッドアンセーフ
よく見る実装static Map<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … }
よく見る実装static Map<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … }
スレッドセーフなMapに変更
Demo
😲
get calc put
check
get calc put
check
get calc put
check
get calc put
check
get calc put
check
get calc put
check
まだputされていない
static Map<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.get(n); if (result == null) { result = PrimeFactor.divide(n); // 素因数分解(重い処理) cache.put(n, result); } // … } Atomicじゃない
FutureTaskを使って遅延評価
FutureTaskを使って遅延評価static Map<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする }
FutureTaskを使って遅延評価static Map<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする }
Callableで処理を記述
FutureTaskを使って遅延評価static Map<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする }
Callableで処理を記述
キャッシュに入れてから処理実行
get calcput
check
get put
check
Demo
😨
get calcput
check
get put
check
calc
get calcput
check
get put
check
calc
get calcput
check
get put
check
新しいFutureTaskで上書きされた
calc
get calcput
check
get put
check
新しいFutureTaskで上書きされた
calc結局2回計算
FutureTaskを使って遅延評価static Map<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { result = new FutureTask<>(() -> PrimeFactor.divide(n))); cache.put(n, result); result.run(); } // (略) result.get()で結果取得できるまでブロックする } Atomicじゃない
ConcurrentMap#putIfAbsent
ConcurrentMap#putIfAbsentstatic ConcurrentMap<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { FutureTask<List<BigInteger>> ft = new FutureTask<>(() -> PrimeFactor.divide(n))); result = cache.putIfAbsent(n, result); if (result == null) {ft.run(); result = ft;} } // (略) result.get()で結果取得できるまでブロックする
ConcurrentMap#putIfAbsentstatic ConcurrentMap<BigInteger, FutureTask<List<BigInteger>>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; FutureTask<List<BigInteger>> result = cache.get(n); if (result == null) { FutureTask<List<BigInteger>> ft = new FutureTask<>(() -> PrimeFactor.divide(n))); result = cache.putIfAbsent(n, result); if (result == null) {ft.run(); result = ft;} } // (略) result.get()で結果取得できるまでブロックする
キーが存在しない場合はnullを、 存在する場合はそれを返す
get calcpIA
check
get pIA
check
pIA … putIfAbsent
get calcpIA
check
get pIA
check
pIA … putIfAbsent
put済みなので上書きしない
Demo
😌
でも面倒くさいね!
😒
Java SE 8から
Java SE 8から
static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, (x) -> PrimeFactor.divide(x)); // … }
Java SE 8から
static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, (x) -> PrimeFactor.divide(x)); // … } キーが存在しない場合はラムダ式
の計算結果を返す
Java SE 8から
static ConcurrentHashMap<BigInteger, List<BigInteger>> cache = new ConcurrentHashMap<>; void doGet(…) { BigInteger n = …; List<BigInteger> result = cache.computeIfAbsent(n, PrimeFactor::divide); // … }
メソッド参照でOK
Demo
Cool!
😎
まとめ•ConcurrentHashMap#putIfAbsent + FutureTaskで効率的なリザルトキャッシュを実装できる 😌
•JDK8からはConcurrentHashMap#computeIfAbsentでおk 😎
Java SE 8を使おう
ご清聴 ありがとうございました