二重化されたオブジェクト(3)
SKKDictionary クラスに BufferedValue version 3.0 を使うとどうなるのか、そのアウトラインを見てみます。
class SKKDictionary: public Dictionary { BufferedValue<EntryContainer> okuriAri_; BufferedValue<EntryContainer> okuriNasi_; void refresh() { EntryContainer tmpOkuriAri; EntryContainer tmpOkuriNasi; // (1) openlab.jp の辞書が更新されているかチェック // (2) 更新されていれば最新の辞書をダウンロード // (3) tmpOkuriAri と tmpOkuriNasi にエントリをロード // (4) 辞書を更新 okuriAri_.set(tmpOkuriAri); okuriNasi_.set(tmpOkuriNasi); } public: virtual std::vector<OkuriganaEntry> findOkuriAri(const CppCFString& query) { // (1) 辞書をコピー EntryContainer dic = okuriAri_.get(); // (2) 検索 } virtual std::vector<CppCFString> findOkuriNasi(const CppCFString& query) { // (1) 辞書をコピー EntryContainer dic = okuriNasi_.get(); // (2) 検索 } };
かなり端折ってますが、おおまかにはこんな感じですね。refresh メソッドはメインスレッドとは別のスレッドから呼ばれることになります。やっぱりこうして見ると、検索の度に辞書をコピーするのが気になります。コードの上ではたったの一行だけど、その裏では大量のインストラクションが一気に実行されるのだから、落ち着かないのも当然です。
さて、勘のいい人ならもう気付いていると思いますが、これは次のように書き換えて最適化することが可能です。
class SKKDictionary: public Dictionary { EntryContainer okuriAri_; EntryContainer okuriNasi_; Mutex mutex_; void refresh() { EntryContainer tmpOkuriAri; EntryContainer tmpOkuriNasi; // (1) openlab.jp の辞書が更新されているかチェック // (2) 更新されていれば最新の辞書をダウンロード // (3) tmpOkuriAri と tmpOkuriNasi にエントリをロード // (4) 排他して辞書を更新 Guard on(mutex_); tmpOkuriAri.swap(okuriAri_); tmpOkuriNasi.swap(okuriNasi_); } public: virtual std::vector<OkuriganaEntry> findOkuriAri(const CppCFString& query) { // (1) 排他 Guard on(mutex_); // (2) 検索 } virtual std::vector<CppCFString> findOkuriNasi(const CppCFString& query) { // (1) 排他 Guard on(mutex_); // (2) 検索 } };
refresh メソッドでは辞書の更新をする直前に Guard(mutex_) で排他するようにしています。また、代入のかわりに swap を使うようにしました。結果は代入と同じですが、swap にはコンテナで不要になったメモリを縮小する効果があります。一方検索では、スコープ全体を排他をしています。辞書の更新を待たせたところで何ら問題はないし、コピーも発生しないのだから、よっぽど効率的ですね。ということで、最終的な BufferedValue の実装は以下のようになります。
BufferedValue version 4.0
いい眺めですねー。随分遠回りをしてしまいましたが、BufferedValue クラスはもう不要です。ここまで長々とお付き合いいただいた皆様には徒労感ばかり残る結果となってしまい、申し訳ない限りです。それでも、余計なクラスを導入せずに済んだのですから、満足しています。今となっては、あの複雑怪奇な BufferedObject にも懐しさを感じるくらいです。
さて、ふりかえって今回の反省点をまとめてみると、以下の二点に集約できるのではないかと思います。
- 手段と目的を履き違えてしまった(辞書の二重化を実現する「クラスの実装」にこだわってしまった)
- パフォーマンス測定をせずに「想像」でコードをチューニングしてしまった
当たり前ですが、どちらも気をつけないといけないことですね。特に「手段と目的の履き違え」は、技術者が繰り返し犯しやすいミスだと思います。なんだか、「またやっちまったよ!」という感じですねぇ... トホホ。
なにはともあれ、BufferedValue はこれでおしまい。次は、CoreFoundation で http クライアントを実装してみるつもりです。