これは「TeX & LaTeX Advent Caleandar 2023」の25日目の記事です。
(24日目はk16shikano さんです。)
DockerでLuaLaTeXして遅くなる件
今年のアドベントカレンダーの初日は次の記事でした。
- 重点解説! LuaLaTeXを遅くしない方法(Qiita/@zr_tex8r)
この記事で「LuaLaTeXがフツーより遅くなるパターン」の2つ目として「Dockerの特性のせいでLuaLaTeXのキャッシュが効いていない」を挙げましたが、それについての詳細はdoraTeX氏の過去の記事にほぼ全て任せました。
-
TeX Live 2019 + 原ノ味フォント + llmk の Docker コンテナを作る(Qiita/@doraTeX)
※この記事の「LuaTeX の初回フォントキャッシュ作成が遅い問題」の節。
その主な理由は「時間切れになったから」「doraTeX氏の記事で十分だと判断したから」ですが、その他に「問題の原因がイメージを“作る側”にあるので、詳細な解説をしてもイメージを“使う側”1は対処のしようがない」という点も挙げられます。
記事の想定読者にイメージを作る能力を仮定しないのであれば、読者にできる対策は「問題への対策が済んでいる(ことが判っている)イメージを使う」ことしかありません。もちろん、例のdoraTeX氏の記事で紹介されているイメージは条件を満たすわけですが、LuaLaTeXを使うという用途を鑑みるとTeX Live 2019は少し古すぎて、これを推奨するのは躊躇われます。ネット上で紹介されているイメージで新しめの環境を使ったものを色々と調べてみたのですが、条件を満たすものが見つかりませんでした。自分の記事の「2つ目のパターン」の記述が中途半端で終わったのはこれが原因だったりします。
ところが実は、ある “裏技”を使うと、ほとんどの(LuaLaTeXが使える)イメージについて“使う側”が対処して「LuaLaTeXが遅くなる」問題を解消することができます。本記事ではその“裏技”を解説します。
前提知識
- LuaLaTeXのキホンを把握している。
- Dockerのイメージを“使う”ことができる。
- イメージを“作る”能力は仮定しない。
イロイロ対策を考えてみる件
doraTeX氏の記事をよく読むと、「LuaLaTeXのフォント関連のキャッシュ」には次の2種類があることがわかります。ここで改めてまとめてみます。
-
フォントデータベース
- これは原則的に2一度だけ生成すればよい。
-
$TEXMFVAR/luatex-cache/generic/names
の下に生成される。 - LuaLaTeXの初回使用時に自動的に生成されるが
luaotfload-tool
で明示的に生成することもできる。 - 従って、“作る側”の対策としては「イメージ作成の際に
luaotfload-tool
を実行する」(①)ようにすればよい。
-
フォントキャッシュ
- 各々のフォントごとのキャッシュデータ。
-
$TEXMFVAR/luatex-cache/generic/fonts/otl
の下に生成される。 - LuaLaTeXで個々の書体を初めて使ったときに自動的に生成される。
- フォントごとのデータなので“作る側”で対処しようとすると結局「使われうる全てのフォントのキャッシュを事前に作る」(②)方法しかない。それをするとイメージサイズが1GB以上増えてしまう。
- “使う側”での対策として「キャッシュの保存先のディレクトリをボリュームにマウントして永続化させる」(③)という方法もある。
③について補足ですが、マウント先(コンテナ側)のディレクトリを$TEXMFVAR/luatex-cache/
としておくと、フォントデータベースとフォントキャッシュの両方がその配下に保存されるため永続化されます。つまり、③はそれだけで完全な対策になっています。
①と②は“作る側”が行う対策なので“使う側”が自分の判断では行えません。対して③は確かに“使う側”が行う対策ですが、これを行うにはマウント先($TEXMFVAR/luatex-cache/
)が具体的にどこにあるのか3を“使う側”が把握している必要があります。従って、“使う側”がフツーのLuaLaTeXを使えるには “作る側”が次の2つの何れかを行っている必要があるわけです。
- イメージを作る際に①と②の対策を実行している。
- “使う側”にマウント先ディレクトリの場所を伝えている。
doraTeX氏は後者の方針をとっていて、マウント先ディレクトリが/usr/local/texlive/2019/texmf-var/luatex-cache
であることを明示しています。
残念ながら、ネット上に公開されているLaTeX用のDockerイメージで“作る側”の対策が実施済のものは、doraTeX氏のもの以外にはなかなか見当たりません。しかし、もし“使う側”はイメージについて以下の2つの情報を得ることができるなら自分で対策を行うことができるはずです。
- イメージを作る際に①と②の対策が行われているか。
- マウント先ディレクトリの場所がどこか。
イメージの情報を取得する件
ここでは“使う側”が先述の“必要な情報”を実験によって得る方法を紹介します。
まず実験用の簡単なLuaLaTeX文書を用意します。
\documentclass{article}
\usepackage{fontspec}
\begin{document}
Hello, world!
\end{document}
さらに luaotfload のログ出力設定を一時的に詳細出力に変更するために、以下の設定ファイルを用意します。
[run]
log-level = 3
その上で、luaotfload.conf ファイルが(コンテナ側の)カレントディレクトリにある状態で hello.tex をlualatex
でタイプセットします。ログファイル hello.log が出力されるはずなので、以下で述べる手順に従ってログの中を調べることで必要な情報を得られます。
なお、TeXのログ出力の仕様のせいで長い行に自動的に改行文字が入ってしまうため、単純に文字列を検索したのでは見逃す可能性があります。注意してください。
①が対策済であるか
①が対策済でなければ、ログファイルに次のメッセージが出力されます。
luaotfload | db : Font names database not found, generating new one.
このメッセージが出力されなければ①は対策済と考えられます。
②が対策済であるか
②が対策済でなければ、ログファイルに次のメッセージが出力されます。
luaotfload | system : saving lua file '/‹何らかのパス›/luatex-cache/generic/fonts/otl/lmroman10-regular.lua' :
このメッセージが出力されなければ②は対策済と考えられます。
マウント先ディレクトリの場所
ログファイルに以下のようなメッセージがあるはずです。
luaotfload | conf : Root cache directory is "/‹何らかのパス›/luatex-cache/generic/names".
このとき/‹何らかのパス›/luatex-cache
が所望のマウント先ディレクトリになります。
実際に実験してみた例
ここでは、日本語LaTeX用のDockerイメージとして比較的有名だと思われる paperist/alpine-texlive-ja について、“必要な情報”を取得してみます。
※実験はWindows(コマンドプロンプト)上で行いました。
まず、前述の hello.tex と luaotfload.conf を同じディレクトリに置いて、そのディレクトリで以下のコマンドを実行します4。
docker run --rm -it --mount type=bind,src=%CD%,dst=/workdir →
paperist/alpine-texlive-ja lualatex hello.tex
※コマンド記述中の→
は「実際には次の行とつなげて1行で入力する」ことを表すものとします。
%CD%
は「現在ディレクトリの絶対パス表記」に展開されるWindowsの環境変数です。bashの$PWD
や$(pwd)
に相当します。
生成されたログファイル hello.log を調べると以下の記述が見つかります。
luaotfload | db : Font names database not found, generating new one.
つまり①の対策は行われていません。
luaotfload | system : saving lua file '/usr/local/texlive/2023/texmf-var/luatex-cache/generic/fonts/otl/lmroman10-regular.lua' :
②の対策も行われていません。
luaotfload | conf : Root cache directory is "/usr/local/texlive/2023/texmf-var/luatex-cache/generic/names".
ボリュームのマウント先のディレクトリは/usr/local/texlive/2023/texmf-var/luatex-cache
です。
結局どう対策すればよいか述べる件
①と②の両方が対策済である場合は“使う側”が対策する必要はありません。提供元の“指示通り”に使用すればフツーのLuaLaTeXが使えるはずです。
そうでない場合は “使う側”が③の対策をする必要があります。実験で判明した「マウント先ディレクトリ」に基づいて、docker
実行時に以下のオプションを追加すればよいはずです。
--mount type=volume,src=ltcache,dst=‹マウント先ディレクトリ›
# "-v (--volume)"オプションで書く場合は↓の通り
# -v ltcache:‹マウント先ディレクトリ›
先の実験に用いた paperist/alpine-texlive-ja の場合、結局以下のようにdocker
を実行すればフツーのLuaLaTeXが使えることになります。
docker run --rm -it --mount type=bind,src=‹現在ディレクトリパス›,dst=/workdir →
--mount type=volume,src=ltcache,dst=/usr/local/texlive/2023/texmf-var/luatex-cache →
paperist/alpine-texlive-ja lualatex ‹ファイル名›.tex
テスト文書は「猫」である件
ここまでに述べた対策によりフツーのLuaLaTeXが使えていることを、実際に短い和文文書をタイプセットして確かめてみましょう。
実験に使うのは例によって(?)『吾輩は猫である』の冒頭の文章です。
% LuaLaTeX文書; 文字コードはUTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxjalipsum}% 例のアレ
\begin{document}
\section{吾輩は猫である}
%『吾輩は猫である』の先頭の10段落を出力
\jalipsum[1-10]{wagahai}
\end{document}
このコードの出力は以下のようなA4判で2ページ半の文書になります。和文2書体(明朝体・ゴシック体1ウェイトずつ)を使ったごくフツーの文書です。
これをlualatex
コマンドで(1回だけ)タイプセットする際の所要時間を調べます。
まずはWindowsでやってみる
Dockerコンテナでの実験の前に、まずは「キャッシュ無効化の影響がどれほどか」を把握するために生のWindows環境で「キャッシュ無効」の状況をシミュレートして時間を測ってみます。
- ①:フツー(キャッシュ有効)にタイプセットを実行する。
- ②:タイプセットの前に
$TEXMFVAR/luatex-cache/generic/fonts
ディレクトリ全体を削除する。フォントキャッシュが無効になる。 - ③:タイプセットの前に
$TEXMFVAR/luatex-cache
ディレクトリ全体を削除する。フォントキャッシュもフォントデータベースも無効になる。
※10回実行して平均と標準偏差を求めました。
テスト | 所要時間 |
---|---|
①フツー | 6.336±0.049 秒 |
②フォントキャッシュ無効 | 12.974±0.071 秒 |
③フォントデータベース無効 | 24.537±0.069 秒 |
フォントキャッシュが無効だと2倍、さらにフォントデータベースが無効の状態だと4倍近くの時間がかかっています。キャッシュが無効であることの影響はかなり大きいことが判りました。
Dockerコンテナでやってみる
それでは本番の実験です。paperist/alpine-texlive-ja のコンテナで先のテスト文書をlualatex
でタイプセットします。
- ①:何も対策をせずにキャッシュ無効の状態でタイプセットする。
docker run --rm -it --mount type=bind,src=‹現在ディレクトリパス›,dst=/workdir →
paperist/alpine-texlive-ja lualatex -halt-on-error test.tex
- ②:ltcache ボリュームをマウントしてキャッシュを有効にしてタイプセットする。LuaLaTeXとしてはこちらの状態がフツーである。
docker run --rm -it --mount type=bind,src=‹現在ディレクトリパス›,dst=/workdir →
--mount type=volume,src=ltcache,dst=/usr/local/texlive/2023/texmf-var/luatex-cache →
paperist/alpine-texlive-ja lualatex -halt-on-error test.tex
結果は以下の通りです。
テスト | 所要時間 |
---|---|
①対策無し(キャッシュ無効) | 12.381±0.209 秒 |
②対策有り(フツー) | 4.381±0.173 秒 |
キャッシュを有効にしてLuaLaTeXをフツーにすることで3倍近く速くなりました!
まとめ
“DockerでLuaLaTeX”している皆さんも、やっぱり遅くないフツーのLuaLaTeXを使って幸せになりましょう!
-
少なくとも「LaTeXを使う」という用途に関しては、「イメージを “使える”人」と「イメージを “作れる”人」の間には大きなギャップがあると思っています。 ↩
-
OpenTypeフォントの加除や luaotfload パッケージの更新があった場合は更新する必要がある。 ↩
-
.
$TEXMFVAR
はTeX Liveが規定する基底ディレクトリの一つですが、その場所はディストリビューションに依存します。UNIX的OSの場合「本家のTeX Live」か「OSのパッケージ」かにより値が異なるのが普通です。TeX Live系以外のディストリビューションの場合はそもそも$TEXMFVAR
という規定もないので、キャッシュのあるディレクトリは「ファイルシステムのどこかにあるluatex-cache/
」ということになります。 ↩ -
カレントディレクトリのbindマウント先が
/workdir
であることはalpine-texlive-jaの仕様として明記されています。コンテナ側のカレントディレクトリは/workdir
であるため、先に述べた“実験の前提条件”が満たされます。 ↩