[SlackLogViewer]Slack過去ログ閲覧ツール更新(8)。

1年近くほったらかしていたSlackLogViewerの更新である。特に機能追加などはないので、主として不具合修正である。

SlackLogViewerの説明はこちらへ
Windows、macOS版のダウンロード先はこちらへ

Qt5のサポート終了

メンテナンスをしていて初めて、OpenSSL 1.1.1のサポートが終了していることを知った。SlackLogViewerはC++とQtを使って作られているが、Qt5はOpenSSL 3.xをサポートしていないことから、Qt6へと完全移行し、Qt5のサポートは打ち切ることにした。Qt6は6.2.4の時点だと若干バグがあったりしたのだが、今では修正されているようだったので、問題はないと思っている。

いやまあ、Qt5でのビルドが全くできなくなったわけではないし、OpenSSL 1.1.1を何処かから拾ってきたりして導入すれば今でもQt5版を使うことはできる。どうしてもQt5が良ければ私は関知しないので自己責任の範囲でご自由にどうぞ。もともと完全自己責任で使うべきOSSではあるので、どのようなトラブルに遭おうが全ては使用者の責任である。と、責任逃れをしておく。

JSONフォーマットの異常への対応

Slackのエクスポートファイルは多数のJSONファイルで成り立っているのだが、これらの中の情報はあちこちが欠損していて、なぜだかメッセージ内容が消えていたり、添付ファイル情報が丸ごと消えていたり、スレッドの親メッセージが消えていたりする。こうした情報欠損は本当に厄介で、どう考えても必須の情報が普通に欠損しているので、「このフィールドが存在しないなんてありえない」、と決めつけた箇所がそこかしこにあった過去バージョンでは頻繁にクラッシュが発生していた。
今回、こうした情報がどれほど不可欠な情報であっても欠損する可能性があると想定したコードに書き換えた。どこかに抜けはあるかもしれないので、そのあたりは見つけ次第直していく。
ただ、GitHubのIssuesに届いている不具合報告のうちどれだけを修正できたかは不明である。これらの修正の多くはすでにテストビルド版として不具合を訴える人たちに提供してみたのだが、あまり直らなかったようなので、まだ色々と不具合が潜んでいる可能性は高い。

Slackが公開範囲の限られたチャットツールである以上、ファイルを開けない不具合はたいてい修正不可能である。エクスポートファイルを提供してもらえないのだからバグの再現ができないのだ。これは私の能力の問題ではなく、単に怠慢なわけでもなく、チャットツールの性格上の問題である。不具合報告自体は受け付けるが、エクスポートファイルを私に公開する勇気のない人は修正されることを期待しないでほしい。

ところで

実は今回の不具合修正のうち大半は去年の8月頃に行ったものである。が、いかんせんその頃から本業のほうが急激に忙しくなって、今の今までリリースに至らず放置されていたのだ。いや、うん、申し訳ない。こっちは時間も金もない中で何とか暇を捻出しながらサポートしている状態なので、今後もバグ報告等に即時対応できる可能性は低い。OSSは利益にならないなぁ、ホント。

[C++]C++20対応のインタプリタを使いたい。Clingの導入。

C++20に対応したインタプリタを使ってみた話。CERNのROOTというデータ分析ツールのプロジェクトチームが同時にClingというC++インタプリタを開発しており、これを導入してみた話である。ClingはLLVMに基づくインタプリタであり、公式にはClangにできることは全てできると宣伝されている。ROOT開発者の言うことなんか信じないぞ!

なお、C++インタプリタについてはxeus-clingROOTに同梱されている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と同じ仕様である。

//testmacro.C
#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。ちょっとだけね。


  1. ROOTの6.28/04以降であれば、自前でビルドすることでC++20を有効化することはできるようだ。公式に案内されているビルド手順に加え、ROOTビルド時のCMakeのオプションに-DCMAKE_CXX_STANDARD=20を与えれば良い。

ADAPT制作に関する所感とか言い訳とか。

前回の記事で、自作のデータ分析ライブラリADAPTの紹介をした。本記事はその開発に関して色々と思うところを書き並べるものである。しょうもない自分語りなので興味のない人は引き返してほしい。

ADAPTは私が大学院生時代からちまちまと作ってきたライブラリである。OpenADAPTはそれらのうち、特に重要な、そして現状C++で他に代替できるものがなさそうなデータ処理部分のみを切り出してゼロから再構築したものだ。元々は自分専用であったり研究室内のみで公開したりと限定的な用途でのみ利用してきたのだが、そろそろ個人での開発に限界を感じてきており、今回、公開へと踏み切った。

元を辿れば、これは私が趣味で作り始めたデータ分析ライブラリだった。しかし私の研究が進むにつれて単なるデータ分析だけでなくデータの3D可視化やハードウェア駆動中の内部データ処理などへ応用範囲が広がり、いつからか研究室に向けて公開するようになった。特に前バージョンは利用者も増え、研究室内で一定の意義と市民権を得ることができた、と認識している。しかし私の研究室は理学系かつITに疎いところなのでこのような開発に興味を持ってくれる人がおらず、誰一人として開発を手伝ってはくれなかった。開発がかなり大規模化し(個人開発ながらソースコードが11万行に達した)博士論文の研究と並行してメンテナンスし続けることに限界を感じていたにも関わらず、その困難を結局誰も理解してくれなかったのだ。理学系研究室の人間はその大半が大掛かりなソフトウェア開発の経験がない、せいぜい数日で出来上がってしまうようなしょっぱいプログラムしか書かない連中なので、そもそも理解する基礎がないのである。ただソフトウェアの価値を彼らが認知してくれるとすれば、研究上直接的に役に立つ場合のみであるというのはまあ当然の話で、ADAPTを応用して作成したソフトウェアならともかく、基礎も基礎のデータ分析ライブラリともなれば実用までがあまりに遠く(それが応用上どれだけ有意義だとしても)理解されないのは仕方がない側面もある。なぜなら、他にも代替する手段がありうるからだ。その代替手段は大抵の場合、大変に不便で応用性や再利用性の欠片もない愚策なのだが、不便の何たるかを知らない彼らにとっては満足できるものなのだ。彼らは確かに無知だが、彼らの無知さにばかり責任を求めるのは酷ではある。
今回ADAPTのオープンソース化に踏み切ったのは、このような環境下で何とか開発を継続するための協力者や理解者を求めたから、という点もある。実際に得られるかどうかは、まあ、博打である。元手が皆無なので負けてもそれほど損失のない博打だ。せいぜい、サンプルプログラムや記事を書いた手間が無駄になるくらい。

C++のデータ分析ツールって需要あるの?という疑問については、正直よくわからないが、一応私の分野では先駆者がいる。私の属す素粒子宇宙系では昔からROOTというCERN開発のフレームワークが使われているのだ。ROOTはスクリプト的に使われることが多いものの、C++のライブラリには違いない。
しかし、分野内でデファクトスタンダードと化しておりしばしば強制的に使わされる私に言わせれば、ROOTはゴミである。ソースコードを見ても何をしているのか分からない、モダンC++とは異なるかなり古めかしい流儀で設計された機能群。グローバル変数や静的メンバ変数が暗黙的に多数使われているらしく、挙動が難解。JOIN相当機能がないのでデータが複数に分かれていたりすると地獄。インタプリタC++標準と異なる挙動をするので混乱を招く。ライブラリとして使おうとするとだいたい動かない。他にも問題点多数。とかく使いにくくて敵わないのだ。歴史が長いだけあって非常に多機能ではあるが、歴史が長いだけあって前時代的な設計になっている。既に素粒子宇宙系の中でもROOTを捨てるグループが多数出てきている。
C++でROOT以外だと、個人開発のものは多少見つかるのだが、私の用途に適してはいなかった。じゃあPythonにすれば、と思わなくもないが、Pythonは素の速度が信じられないほどに遅いので、大規模なデータをまともに扱えない。過去幾度となくPythonでデータ処理させようとしてはその鈍足さに絶望してきた私にとって、あれに移行する選択肢はない。よって、私は自作することにした。

ADAPTは、ライブラリと呼ぶことさえ烏滸がましい最初期のがらくたを除いて、今回公開したものが都合3バージョン目である。
最初に作ったものは実質的なプロトタイプで、バージョンは0。階層構造は当時から採用されていたが、いかんせんC++歴1年程度の私の知識では真っ当なものは作れなかった。
一応このときの経験から様々な知見を得ることが出来、再設計されたのがバージョン1系、現在研究室において主として稼働しているバージョンである。しかし十分に整理することはできず複雑怪奇なものになってしまった上、随所に原因不明のバグも発生した。特にJoinの仕組みなどは1.Y系で本格的に導入されたのだが、C++歴2年ほどで言語を使いこなせていなかった当時の私ではどうしても限界があった。とはいえ機能的には充足していたこともあって、私の学生時代はこのバージョンで乗り切ることが出来た。
バージョン2系―――これが前記事で紹介したOpenADAPTに相当するものなのだが―――は、獲得した知識と経験だけを残しそれ以外の資産をまるごとスクラップにすることで出来上がった。過去のコードはほとんど残っていない、ゼロからの構築だった。コンセプトはともかく設計が愚かしい前バージョンから前進するには、丸ごと捨て去るしかなかった。
今の今までオープンソースにもせず、研究室の一部範囲に対してのみ公開してきたことを思うと、ずいぶん暇なことをしていたんだなぁと思わなくもない。いや、研究上必要だったから作ったんだけどね。

ADAPTは現代のデータベースで最も普及しているテーブルを基本とした関係モデルではなく、階層型+Joinという変則的な仕組みを採用している。そんなものが本ライブラリ以外に存在するのか分からないが、私の用途ではこれが便利だった。予め構造化された数百万から数十億件程度のデータ分析をこなす中で速度と柔軟性を両立しようとして、現状ではこのような構造に行き着いているのである。とはいえ私はコンピュータサイエンスに関しては素人なので、これが私の目的に対して真に最適な方法なのかは判断しかねる。
私が階層型を採用したのは、私の扱うデータは既に構造が確立されており、いちいち組み替える必要のないものばかりだったからだ。そこで、一度データを読み込んでしまえばデータの改変はほとんど起きないことを前提に、走査と抽出の速度に特化した設計を考える中で、今の形に行き着いた。ただ汎用性はPandasなどのほうが遥かに高いことだろう。ADAPTの場合、親子関係の組み換えや新たなフィールド追加などしようものなら新たにツリーを生成するのに匹敵するコストがかかる。ただ将来的には必要になる気はするので、何とかコストを最小化して実装したいとは思っている。
何にせよ、汎用性とパフォーマンスの改善は今後の課題である。そもそも今の設計が、私の求めるパフォーマンスを満足しているのかもテストできていない。旧バージョンより遅くなったりしたら目も当てられない。このあたりは、今後私の研究に本格導入していく中で改善していく。

長年開発してきたライブラリを公開するので、さすがにちょっと緊張している。SlackLogViewerなどは作り始めた時点で間違いなく需要があると確信できたが、こちらはさっぱりである。Pythonによるデータ分析全盛期の現代に、わざわざC++を、それも個人開発のライブラリを使いたがる人間がどれだけいるというのか。しかし、私自身が欲しかったのだから作ったのだし、せっかく作ったものをいつまでも非公開で放っておくのも非生産的ではある。私と似たような悩みを抱えつつ、しかし自主開発するほどのコストを払えずに悶々としていた人のもとに届くことを願って、本記事を投稿する。