本記事は「ものすごく点の多いデータを GNUPLOT + TikZ + PDFLaTeX で処理しようとして、メモリーが溢れる場合の対処法」として公開していたものを大幅に書き直した記事です。
tikzexternalize とは
tikzexternalize を使用すると、TikZ による図それぞれに対し単体の PDF ファイルを生成し、二回目以降は (内容に変更がなければ) その PDF を単純にインクルードすることで、コンパイル時間やコンパイル時の使用メモリー量を減らすことができます。
例えば、gnuplot などで生成した複雑な (点が多いなど) グラフが多く含まれる文書で、一つ一つのグラフのコンパイルにそれなりに時間がかかる場合に便利です。
具体的には以下のような感じで使用します。
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize[prefix=_tikz/]
これで、以降に出てくる TikZ の図がそれぞれ PDF として生成されます。
ここで \tikzexternalize
中の prefix
はキャッシュファイルが置かれるディレクトリーで、コンパイル前に作成しておく必要があります。省略時はカレントディレクトリーになります。一つの図につき 4 つのファイルが生成されるので、特に図が多い場合には prefix
を指定した方が良いでしょう。
コンパイルする際には、-shell-escape
オプションが必要です:
pdflatex -shell-escape [file]
Emacs で AUCTeX を使っている場合は、以下をファイルの最後あたりに入れておくと嬉しいかもしれません (pdfLaTeX を使用する場合の例)。
%%% Local Variables:
%%% TeX-master: t
%%% TeX-PDF-mode: t
%%% LaTeX-command: "latex -shell-escape"
%%% End:
tikzexternalize はなにをやっているのか
起動した LaTeX コマンドの中からさらに LaTeX コマンドを起動して、図の PDF ファイルを作成します。
その前にキャッシュの有無を調べ、もしある場合には MD5 ハッシュを計算し変更の有無を調べ、新規もしくは変更されているもののみコンパイルが実行されます。ハッシュ計算の際にはコメント等は除外されているようなので、例えばコメント中に書かれたタイムスタンプのみが変更されているような場合では再コンパイルは発生しません。
一回目と二回目のコンパイルログを比較してみると、以下のようになります。二回目は変更がなかったことを検出し、すでに生成されている PDF ファイル (./_tikz/external-test-figure0.pdf
) が読み込まれていることが分かります。
(一回目)
===== 'mode=convert with system call': Invoking 'pdflatex -shell-escape -halt-on-error -interaction=batchmode -jobname "_tikz/external-test-figure0" "\def\tikzexternalrealjob{external-test}\input{external-test}"' ========
This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019) (preloaded format=pdflatex)
\write18 enabled.
entering extended mode
[1{/usr/local/texlive/2019/texmf-var/fonts/map/pdftex/updmap/pdftex.map} <./_tikz/external-test-figure0.pdf>] (./external-test.aux) )
(二回目)
===== Image '_tikz/external-test-figure0' is up-to-date. ======
[1{/usr/local/texlive/2019/texmf-var/fonts/map/pdftex/updmap/pdftex.map} <./_tikz/external-test-figure0.pdf>] (./external-test.aux) )
キャッシュファイル名と \tikzsetnextfilename
上の例からも分かるように、デフォルトでは [.tex ファイル名]-figure0
のような連番が付けられます。ですので、内容に変更がなかったとしても、図の順番を入れかえただけで再生成のための latex が実行されてしまうことになります。そのため、キャッシュのファイル名も指定した方が嬉しい場面がよくあります。
その為には \tikzsetnextfilename{ファイル名}
を使用することができます。これを対象の図の直前に書けば、キャッシュは ファイル名
で生成されます。\tikzsetnextfilename
の引数に dir/graph
のような指定をすることもできますが、コンパイル前にキャッシュディレクトリーに dir/
を作っておかないとエラーになります。
ただ、毎回指定するのは面倒なので、私は図は必ず別ファイルにし、それを以下のコードでインクルードする、としています (本文中に書いた tikzpicture については、コンパイルにさほど時間がかからないと仮定し、気にしないことにする)。この例では、ディレクトリーによらずファイル名はユニークであることを仮定しています。(つまり dir1/graph.tex
と dir2/graph.tex
があると、キャッシュ名が衝突するので上手くいきません。)
\newcommand\figinput[2][./]{\tikzsetnextfilename{#2}\input{#1#2}}
% 本文中で
\figinput{graph} % graph.tex を取り込む
% あるいは
\figinput[dir/]{graph} % dir/graph.tex を取り込む。"/" は必須
余談: 私自身は、実際にはキャッシュファイル名にもディレクトリーを指定できるようにしています。また、ディレクトリー名には必ず /
を付けなければならないのは美しくありません。これらの点において上記コードは相当に改善の余地アリです。
externalize の制御
以下を知っておくと便利かもしれません:
\tikzexternaldisable
: externalize を無効にする\tikzexternalenable
: externalize を再有効化する\tikzset{external/export=boolean}
: これ以降、現在のスコープ内で externalize する (true
) / しない (false
)\tikzset{external/export next=false}
: 次の対象 (のみ) を externalize しない
例えば、chemfig で反応機構を描く場合などはうまく externalize できません(*1)。この場合は、対象の図の直前に、
\tikzset{external/export next=false}
を入れて一時的に externalize を無効化することで回避できます。
(*1) おそらく remember picture を使ってる場合は一般にうまくいかないと思います。
なお、\tikzset{external/export=false}
としておいて externalize したい図だけ \tikzset{external/export next=true}
とする、というのはできないようです。
注意点
- externalize される図が多いと、一回目はものすごく時間がかかることがあります (図の数 + 1 回の latex が実行されるので)。図を追加したらこまめにコンパイルしておいた方が良いでしょう。
- externalize を使用しているときにコンパイルエラーが発生すると、その原因を掴むのに苦労することがあります。図の方に問題がある場合は、メインファイルのログ (だけ) ではなく、キャッシュ側のログを見る必要があります。この意味でも、図を追加したらこまめにコンパイルしておいた方が良いと思います。
- 最終プロダクトでは externalize を外すか、キャッシュを削除して再コンパイルするのが無難かもしれません。
原因は追えていないのですが (ほら、問題が発生するのはたいてい締切前な訳で…)、図を更新したのにキャッシュが更新されないということがありました (気のせいでなければ…)。