C++20に対応したインタプリタを使ってみた話。CERNのROOTというデータ分析ツールのプロジェクトチームが同時にClingというC++インタプリタを開発しており、これを導入してみた話である。ClingはLLVMに基づくインタプリタであり、公式にはClangにできることは全てできると宣伝されている。ROOT開発者の言うことなんか信じないぞ!
なお、C++インタプリタについてはxeus-clingやROOTに同梱されているClingなどもあるのだが、前者はC++20へ対応出来ていない様子で、後者はpre-compiled binaryがC++17までしか対応していない1上にROOTの本体が付随してきて鬱陶しいので使わない。ただし、ROOT本体がくっついてきても特に気にならない、C++20が必要ないのならROOTのpre-compiled binaryをインストールするのが最も楽である。
今回はWSL(Ubuntu 22.04)上で行った。いずれWindows上でも試してみたいが、果たして。
Clingのビルド
Clingはビルド済みバイナリも配布しているようであるが、こちらは2020年11月を最後に更新が止まってしまっているし、そもそもWindows用は配布されていなかった。当然最新のLinux向けも存在しない。なので、ソースコードからビルドすることにする。
Git、GCC、CMake、Ninjaあたりは事前にインストールされているものとする。GCC 13で試した。
なお、CMakeではビルド時の一時ファイル保存先とインストール先のディレクトリを分けることが多いと思うが、Clingの場合は分けないほうが良い。ビルド時のファイルとインストールした先のファイルの双方を残しておく必要があるためだ。ビルドの際に作成された何かしらのファイルを実行時にも要求するらしく、削除したら動かなくなった。
git clone -b cling-llvm16 https://github.com/root-project/llvm-project.git llvm_src
git clone https://github.com/root-project/cling.git cling_src
mkdir cling
cd cling
cmake -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_TOOLS=Off -DLLVM_EXTERNAL_PROJECTS=cling -DLLVM_EXTERNAL_CLING_SOURCE_DIR=../cling_src -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ../llvm_src/llvm
cmake --build . -j4
cmake --build . --target install
必要であれば、.bashrc
に次のように追記しclingのインストール先にパスを通しておく。
export PATH=$PATH:/path_to_cling_install_dir/bin
動かしてみる
WSL上でcling
と入力すると、インタプリタが開始する。あとは#include ...
やらstd::vector<double> v...
やら必要なコードを一行ずつ書いていけばその通りに動く。
ROOTでは色々なヘッダが予めincludeされていたりusing namespace std;されていたりと鬱陶しかったが、Clingではそのようなことはないらしい。
ライブラリを読み込ませる
Clingは外部からヘッダを読ませたり、共有ライブラリを読ませたりできるので、サードパーティのライブラリなども共有ライブラリを使えるのなら実行可能である。cling実行時にオプションで渡すだけで良い。
cling -I/path/to/include/directory -L/path/to/lib/directory -lyourlibname.so
試しにMatplot++を共有ライブラリにして呼んでみたが、ちゃんと動いた。
ただしスタティックライブラリは対応していない。サポートすること自体は可能らしいが、人手不足によって進んでいない様子だった。
マクロにして与える
一般的なインタプリタ言語のように、テキストファイルでマクロを渡して実行させることももちろんできる。
マクロのファイル名と同じ名前の関数を定義しておくことで、cling実行時にその関数が実行される。ROOTと同じ仕様である。
#include <iostream>
void testmacro()
{
std::cout << "testmacro" << std::endl;
}
あとはこれを.x
オプションでclingに渡せば良い。
cling .x testmacro.C
確認したバグ
マクロにまとめて実行したときは問題なかったが、一行ずつインタプリタにて入力しているとき、構造化束縛がうまく動かなかった。構造化束縛で宣言した変数にアクセスすると、undeclared variable
扱いされるのだ。現代のC++でこれを使えないのはちょっと厳しい。後日Issueを投げておこうと思う。
また、cling実行時ではなく、既に実行されているclingの中で.x
オプションでマクロを与えてもちゃんと動くのだが、同じマクロを連続して(clingを終了することなく続けて)再実行すると、謎のエラーを吐いて落ちる。これについてはエラーメッセージが意味不明だったので、よくわからない。
閑話
C++はコンパイル型言語である。これは揺るぎない事実である。私の用途上、プログラミング言語は速度に優れていることから分野内でデファクトスタンダードと化しているC++を選択せざるを得ないので、私は常にコンパイルにそれなりの時間的コストを支払いながら作業している。もちろん、それ相応に実行時のコストが減るので、別に言語自体に不満を持ってはいない。
しかし、たまにはインタプリタ型言語的にインタラクティブに何かを実行したいときもある。科学計算系ではこういうとき、主にPythonが使われる。ただPythonには 死ぬほど遅い という地獄のような欠点が常に付きまとう。
スクリプト言語的に使いたい、しかし時にはコンパイルして速度を出したい。これを両立する良い方法はないものだろうか?もちろんC++とPythonを使い分けるのも良い。ただ普段C++で使っているライブラリをPythonで呼び出す、あるいはその逆をしようと考えると、それはそれで余計な悩みが増えたりもする。私はPybind11で自作ライブラリのPythonバインディングを書いたことがあるが、あれもバージョン互換性など色々な問題でコンパイルエラーを起こしたりするので、それほど容易ではないのだ。またファイルの読み出しの部分をPythonで書けない(Pythonからデータ格納処理を呼ぶと恐ろしく時間がかかる)という問題も発生し、なかなか使いづらい状況が出来上がってしまった。
一番嬉しいのは、普段使っているライブラリなども全てひっくるめて、C++の文法そのままにインタラクティブに実行できることだ。ちょっとしたコードはスクリプト的に動かして、計算が遅ければ全く同じコードをただコンパイルするだけでよい。そんなことができれば日頃の作業が格段に楽になる。Clingは案外、その理想に近い所まで来ているようだった。先日公開したOpenADAPTが、意外とそのまま動いてしまったのだ。完全ではないが、このサンプルコードのQuickStart_dtreeがほぼそのまま動いた。C++20を随所に取り入れておりそれなりにややこしい設計になっているこれが普通に動いてしまうとは……。まあ一部手直しした部分もあったし、QuickStart_streeは動かなかったし、奇妙なバグも見つけはしたが、普段解析をするときはDTreeが動作すれば事足りるので、それほど大きな問題ではない。
ただ流石にヘッダオンリーで書いてしまったせいで、実行時には数秒程度であるがコンパイル時間がかかる。また最適化オプションもあるものの実行速度は通常のデバッグビルドに毛が生えた程度らしい。やはり部分的にでも共有ライブラリ化できるように整理したほうが良いかもしれない。
こんなしょうもないことをしているのも、周囲の人間がROOTを使ってインタラクティブにデータ分析しているのを傍で眺めて「俺もあれやりたい!」と思ってしまったからだ。私はROOTが嫌いでずっと使ってこなかった人間なので、今まではC++とPythonを組み合わせて何とかしようとしてきた。が、もう何度も何度も何度も何度もPythonのナメクジみたいな遅さに足を引っ張られ続け、ついにはPybind11が謎のエラーを吐いて自作モジュールのビルドすら一筋縄ではいかなくなり、いい加減Pythonが嫌になったのだ。
そんなわけでC++のインタプリタなるどこの誰が使うんだか分からないもんに手を出しているわけである。ROOTはPythonからも動くようになったのに、未だにC++で書かれることが多いのは、ビッグデータ解析のためにPythonなんか使ってられない業界もある、ということなのだろう。日頃からTB単位のデータ処理、解析を行っている私も多分似たようなものだ。Clingのおかげで、普段の些細な解析はインタプリタで、大規模な処理はコンパイルして、と使い分けることが出来そうで、希望が見えてきた。ちょっとだけ見直したよ、ROOT。ちょっとだけね。