キャッシュ

キャッシュ機能の使用例について

オリジナルのページ: https://yihui.org/knitr/demo/cache/

オリジナルの更新日: 2011-12-04


チャンクオプションの cache=TRUE でキャッシュを有効にでき, cache.path でキャッシュ用ディレクトリを指定できます. 1 章『オプション』を参照してください.

キャッシュの例

キャッシュ機能は私の文書の多くで広く使われています. たとえば knitrメインマニュアルグラフィックス です. ここでは, もう少しいくつかの例を挙げます.

  • 基本的な例
  • キャッシュの依存関係の自動的な構成
    • Rnw ソース: knitr-dependson.Rnw
    • チャンクオプション autodep=TRUE と関数 dep_auto() によって, knitr チャンク間の依存関係を解決することが可能となり, dependson オプションを指定するための手作業をいくらか省くことができます.

重要な補足事項

キャッシュがいつ再構成されるかと, キャッシュされないチャンクについてよく理解するため, メインマニュアル (英語版 PDF) のキャッシュに関するセクションを, とても注意深く読まなければなりません.

キャッシュに影響する要素を今一度繰り返します (いかなる変更も古いキャッシュを無効にします):

  1. include を除く全てのチャンクオプション, たとえば tidy=TRUEFALSE に変えるだけでも古いキャッシュは破棄されますが, include は例外です.
  2. チャンク内のR コードのわずかな変更, スペースや空白行の追加・削除であっても, 古いキャッシュを削除することにつながります

極めて重要な注意事項として, 副次的な作用をもつチャンクはキャッシュすべきでないということを挙げます. knitrprint() による副次作用を維持しようとしますが, さらなる別の副次作用は保存されません. ここでチャンクをキャッシュすべきでないケースをいくつか挙げます.

  1. options('width'), pdf.options(), または opts_chunk$set(), opts_knit$set(), knit_hooks$set() のような他のなんらかの knitr のオプションを設定する時
  2. キャッシュされたチャンクでの library() によるパッケージの読み込みと読み込まれたパッケージはキャッシュされないチャンクによっても扱われます. (キャッシュされたチャンクでパッケージを読み込み, 使うのは全く OK です. knitr はキャッシュされたチャンクでパッケージのリストを保存するためです. しかし, キャッシュされないチャンクが, それまでのキャッシュされたチャンクでどのパッケージをロードしたかを知ることはできません.)

さもなければ次回からはチャンクはスキップされ, そこでの設定はすべて無視されます. このようなチャンクでは明示的に cache=FALSE を使わなければなりません.

source()setGeneric() は, コードがローカル環境で評価された場合でもグローバル環境にオブジェクトを作成するという副次作用を持ちます. knitr 0.4 より前ではこれらのグローバルオブジェクトをキャッシュすることはできませんでした (たとえば issue #138 を見てください). しかし ver. 0.4 以降は knitrglobalenv() で作成されたオブジェクトを確認し, 他のオブジェクト同様に保存するようになったため, キャッシュできるようになりました.

キャッシュされたチャンクで使われるパッケージのリストは保存されますが, パッケージ名をキャッシュする方法としては完璧ではありません. かりにパッケージを読み込み, あとで除外したとしても, knitr はそれを知ることはできません (新たに読み込まれたパッケージしか捕捉できません). Issue #382 で解説されているように, キャッシュディレクトリにある __packages ファイルを手動で編集しなければなりません.

キャッシュにはまだ何かありますか?

以上のオブジェクトがキャッシュに影響するのはいかにも納得できることですが, 再現可能性のある研究ではキャッシュは他の変更によって無効化されるという点でよりいっそう厳格になることがあります. 典型例の1つはソフトウェアのバージョンです. 2つのバージョンが異なるRに, 異なる結果を出させるのは不可能ではありません. この場合, 我々はこう設定するかもしれません.

knitr::opts_chunk$set(cache.extra = R.version.string)
knitr::opts_chunk$set(cache.extra = R.version) # あるいはプラットフォームの違いも考慮する

これによって, キャッシュされた結果は特定のバージョンの R でのみ適用されます. R をアップグレードし文書を再コンパイルしたとき, 全ての結果は再計算されます.

同様に, キャッシュが特定の環境でのみ保存されるように, このオプションに他の変数も設定したいかもしれません. これは野心的な例です.

## キャッシュは特定の R のバージョンとセッションでのみ有効
## キャッシュは最大で1ヶ月間保存される (来月は再計算される)
knitr::opts_chunk$set(cache.extra = list(
  R.version, sessionInfo(), format(Sys.Date(), '%Y-%m')
))

この cache.extra オプションの別の良い使用例が issue #238 で示されています. この例ではキャッシュはファイルの更新日時と関連付けられています. つまり, データファイルが変更されれば, キャッシュは自動的に再構成されることになります31.

注: キャッシュ条件にさらにオブジェクトを導入する際に, cache.extra 以外の任意のオプション名を使用することができます. たとえば cache.validation も呼び出せます. 全てのチャンクオプションはキャッシュの確認時に考慮されるためです.

キャッシュディレクトリを入力ファイル名と連動させる

ときとしてデフォルトとは異なる入力ファイルに対して異なるキャッシュディレクトリを使いたいことがあるかもしれません. 解決策の1つが issue #234 で提示されています. しかし, 自己完結性を高めるため, この設定はソースドキュメントの内部で行うことを推奨します (opts_chunk$set(cache.path = ...) を使ってください.).

より細かいキャッシュのとり方

上級者はチャンクオプション cache に対して TRUEFALSE かだけでなく, 数字で設定するなどしてもっと粒度の細かいキャッシュが欲しいと考えるかもしれません. cache = 0, 1, 2, 3 として, 0FALSE, 3TRUE と同じで, cache = 1 は計算の結果のみキャッシュから読み込む (evaluate::evaluate() から), よってコードは再評価されませんが, 他の箇所, 出力フックや作成されたグラフの保存といった箇所は評価されます. cache = 2 ならば, 1 とほとんど同じですが, 唯一の違いはグラフファイルがすでに存在する場合, 再保存されない点です. これはグラフが大きい場合, いくらか時間の削減になります. 以前のRセッションで記録されたグラフが別のRセッション, あるいは別のバージョンで安全に再保存されるという保証はないため, cache = 1 よりは cache = 2 を推奨します.

cache = 1, 2 の場合は少数のチャンクオプションだけがキャッシュに影響します. オプション名は knitr:::cache1.opts, knitr:::cache2.opts で確認してください. 基本的に, コード評価に影響しないチャンクオプションが変更されてもキャッシュは無効になりません. たとえば echoTRUE から FALSE に変えるとか, fig.cap = '新しいキャプション' と設定するなどの変更です. しかし, evalTRUE から FALSE に変えた場合, あるいは cache.path='foo/''bar/' に変えた場合, キャッシュは再構成されます.

いくつかの例が, example #101 (出力) で見られます.

この方法では, 計算と文書の出力レンダリングを分離することが可能となり, キャッシュを破棄することなく出力を調整するのに役立ちます. issue #396, #536 を確認してください.

乱数生成器 (RNG) の再現可能性

乱数生成器 (RNG) が関係するチャンクの再現性を維持するため, knitr.Random.seed をキャッシュし, 各チャンクの評価直前に復元します. しかし1つ問題があります. チャンク A, B がキャッシュされていたとします. この時A, B の間に新たにチャンク C を挿入したとします (3つのチャンクは全て内部で RNG を使用しています). RNG が変更されたため B が更新されるはずですが, .Random.seed は副次作用であるため, これに反して実際には B は更新されません. はっきり言うと, B の再現性は偽りのものとなります.

RNG の関係する再現可能性を保証するために, .Random.seed とキャッシュを関連付ける必要があります. これが変更される度にキャッシュも更新されなければなりません. これは cache.extra オプションで 評価されない R 評価式を参照することで簡単にできます. たとえば以下のように.

knitr::opts_chunk$set(cache.extra = rand_seed)

?rand_seed を確認してください (これは評価されない R 評価式です). この場合は各チャンクは最初に .Random.seed が最後の実行から変更されたかどうかを確認します. .Random.seed が異なるならキャッシュは再構成されます.


  1. 訳注: ファイルの更新日時を取得するには base R の file.mtime 関数が便利とのことです↩︎