「CSS完全設計ガイド」はとても丁寧に作られた良本でした
去年発売された本ですが、今さら読みました。 gihyo.jp
読んだ動機
私はフロントエンド、特にデザイン周りはからっきしです。
フロントエンドやるときはJavascriptのチューニングばっかりなので、HTML+CSSは最低限知っている程度のレベルです。
WebスクレイピングをしているとCSSクラスの指定が結構複雑で「何かベストプラクティスがあるんだろうなぁ」と思っていまいた。
そこらへんの知識をちょっと除き見して、あわよくばスクレイピングで利用したかったのです。
感想
HTML + CSSをかじったことのある程度の私にはちょうど良い本でした。
とても丁寧に作られている本だと思ったので、本来の対象読者であるHTML+CSS書く人以外でもお勧めだと思います。
500ページ以上ある大作ですが、HTML+CSSのコードが大量に掲載されているためなので、そんなに重くなく読み進めることができます。
かといって「HTML+CSSのコードが貼りまくられてページ数水増ししている本」にならないように、「意味が伝わる必要最低限のコード」を筆者が気を配って用意しているのを強く感じます。
この方針が破綻なく最後まで続くので、ほんと凄いなぁと思いました。
おおざっぱな中身の紹介
導入部分ではCSS設計の重要性を話した上で、Atomic Designの紹介を経てOOCSS,SMACSS, BEM, PRECSSの紹介と続きます。
その後、BEM, PRECSSでの具体的なコンポーネントの設計・実装例に移ります。
この話の流れも自然ですっと理解できました。
- レイアウトと装飾のクラスは分けて指定する
- Modifier, Mixinのクラス名はスコープを明示する
とかは確かにスクレイピングで見るHTMLで見かける内容だなぁと追体験できました。
設計を原理主義として話すだけではなく、冗長になる部分や設計の境界を迷う部分についても解説されているのも納得感が高かったです。
後半部分でコンポーネントを組み合わせて複雑なレイアウトを実現する際に、CSSの再利用を多用していく部分なんかは自分で書けたらなかなか気持ち良さそうに思いました。
設計の重要性を感じとれる瞬間ですね。
最後にツールの紹介もされているので、CSS設計についてなんとなく全体を俯瞰できた感じがします。
恥ずかしながら、CSS MQ Packerを安易に適用すると駄目なこととか、スタイルガイドという言葉自体初めて知りました。
まとめ
最初にも書きましたが、とても丁寧に作られた本だなぁと思いました。
普通のプログラミングとは異なりかなり制約がきつい世界なのですが、様々な人の知見で設計手法が作り出され、再利用性が確立されているのを知れただけでも良かったです。
文字列から浮動小数点数に変換する、なるべく速く
TL;DR
文字列から浮動小数点数に変換するならfastfloat使いましょう。
私が試せる環境で比較する限り、とても速いです。
細かいことが気になります
C++でちょっとしたプログラムを書くときにいつも気になるのが
「文字列データから指定データ型への変換処理をどうやって効率的に書くか」
です。私だけかもしれませんが。
特に悩んでしまうのが「文字列→浮動小数点」です。
- std::scanf, std::stringstreamを使うものは大抵すごく遅い
std::strtodstd::stodはstd::stringへの変換が入るので避けたい- std::from_charsは(libstdc++が)浮動小数点型に対応していない
- boost::sprit::qiが何故か速いのだけれどこのためにboost::sprit使うのは重い
と色々制約が多いのです。どうにかならないものか。
fast_floatの紹介
…と思っていたら見付けたのがsimdjsonの作者であるlemireさんが開発しているfast_floatです。 github.com
もともとはfast_double_parserとして開発されいて、こちらはGo言語に移植されてstrconv.ParseFloat
として標準化されているようですね。そんな経緯があるとは知らなんだ。
Microsoft LightGBMでも利用されているようで。
GitHub - lemire/fast_double_parser: Fast function to parse strings into double (binary64) floating-point values, enforces the RFC 7159 (JSON standard) grammar: 4x faster than strtod
このfast_double_parserをstd::from_charsのAPIに寄せて書き直したものがfast_floatになります。
こちらの実装はApache ArrowやYandex ClickHouseに利用されているようですね。モテモテ。
さらに最近lemire/fast_floatからfastfloat/fast_floatに移動して、少しずつですが更に高速化しています。
ベンチマークのプログラムの準備
「どのくらい速いのか?」については、lemireさんがベンチマークを公開しています。
github.com
ベンチマークを実行すると以下のライブラリのランダムな浮動小数点数表記の文字列のdoubleへの変換速度を計測してくれます。
- double conversion https://github.com/google/double-conversion
- netlib http://www.netlib.org/fp/の関数名を変更したもの
- strtod C++の標準ライブラリ
- abseil https://github.com/abseil/abseil-cpp内の
absl::from_chars
このベンチマークを実行するだけだと芸がないので、以下のベンチマークも追加しています。
- boost::spirit:qi
- fast_float (fixed) 指数表記以外の10進数表記に対応したロジック
GitHub - toge/simple_fastfloat_benchmark
ベンチマークのコードは、コードを内包したりCMakeのFetchContent機能を使っているので、ライブラリを別途インストールする必要ありません。
git, cmake, gccがあれば、お手軽に試せます。
git clone https://github.com/toge/simple_fastfloat_benchmark cd simple_float_benchmark export CXXFLAGS="-O3 -march=native" cmake -B build . cmake --build build --config Release build/benchmarks/benchmark
ベンチマークの結果
fastfloat (fake)
っていうのは「パースだけして数値計算しない」ロジックなので理論値みたいなものですかね。
AMD Ryzen 1700(自作PC) gcc 10.2.1
fastfloat (fake) : 1595.02 MB/s (+/- 3.7 %) 76.02 Mfloat/s 9.14 i/B 201.00 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 76.1 %) 1.99 c/B 43.75 c/f (+/- 3.3 %) 4.59 i/c 3.33 GHz netlib : 215.83 MB/s (+/- 2.5 %) 10.29 Mfloat/s 32.26 i/B 709.76 i/f (+/- 0.0 %) 0.19 bm/B 4.14 bm/f (+/- 0.1 %) 14.70 c/B 323.33 c/f (+/- 2.2 %) 2.20 i/c 3.33 GHz doubleconversion : 130.89 MB/s (+/- 3.2 %) 6.24 Mfloat/s 56.35 i/B 1239.78 i/f (+/- 0.0 %) 0.12 bm/B 2.75 bm/f (+/- 2.0 %) 24.26 c/B 533.80 c/f (+/- 3.0 %) 2.32 i/c 3.33 GHz strtod : 141.70 MB/s (+/- 1.9 %) 6.75 Mfloat/s 51.91 i/B 1142.06 i/f (+/- 0.0 %) 0.13 bm/B 2.79 bm/f (+/- 0.3 %) 22.45 c/B 493.82 c/f (+/- 1.5 %) 2.31 i/c 3.34 GHz abseil : 342.08 MB/s (+/- 5.3 %) 16.30 Mfloat/s 30.11 i/B 662.51 i/f (+/- 0.0 %) 0.02 bm/B 0.50 bm/f (+/- 14.1 %) 9.27 c/B 203.92 c/f (+/- 5.2 %) 3.25 i/c 3.32 GHz boost sprit qi : 585.51 MB/s (+/- 5.7 %) 27.91 Mfloat/s 24.86 i/B 547.00 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 72.1 %) 5.42 c/B 119.23 c/f (+/- 5.3 %) 4.59 i/c 3.33 GHz fastfloat : 1116.40 MB/s (+/- 5.9 %) 53.21 Mfloat/s 11.64 i/B 256.04 i/f (+/- 0.0 %) 0.00 bm/B 0.01 bm/f (+/- 2.6 %) 2.85 c/B 62.61 c/f (+/- 5.3 %) 4.09 i/c 3.33 GHz fastfloat fixed : 1339.68 MB/s (+/- 2.5 %) 63.85 Mfloat/s 9.86 i/B 217.02 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 3.9 %) 2.38 c/B 52.27 c/f (+/- 1.7 %) 4.15 i/c 3.34 GHz
Linuxだとperfの計測結果まで付与してくれて便利ですね。 Zen3でどうなるのか気になる…。(Ryzen 5800Xください)
Intel Core i5-8210Y(Macbook Air 2018) apple-clang 12.0.0
fastfloat (fake) : 1314.89 MB/s (+/- 19.2 %) 62.67 Mfloat/s 15.96 ns/f netlib : 262.55 MB/s (+/- 16.2 %) 12.51 Mfloat/s 79.91 ns/f doubleconversion : 220.23 MB/s (+/- 17.5 %) 10.50 Mfloat/s 95.27 ns/f strtod : 67.26 MB/s (+/- 9.2 %) 3.21 Mfloat/s 311.92 ns/f abseil : 412.25 MB/s (+/- 13.9 %) 19.65 Mfloat/s 50.89 ns/f boost spirit qi : 426.88 MB/s (+/- 14.3 %) 20.35 Mfloat/s 49.15 ns/f fastfloat : 886.46 MB/s (+/- 14.2 %) 42.25 Mfloat/s 23.67 ns/f fastfloat fixed : 1076.13 MB/s (+/- 18.9 %) 51.29 Mfloat/s 19.50 ns/f
MacOS Xだからperfがないのであっさり表示。
strtod
の衝撃の遅さが目を惹きます。
Intel Core i5-4300U(Let's Note LX3) gcc 10.2.0
fastfloat (fake) : 885.05 MB/s (+/- 3.7 %) 42.18 Mfloat/s 9.59 i/B 211.00 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 47.6 %) 3.11 c/B 68.32 c/f (+/- 1.0 %) 3.09 i/c 2.88 GHz netlib : 197.58 MB/s (+/- 1.6 %) 9.42 Mfloat/s 30.46 i/B 670.18 i/f (+/- 0.0 %) 0.19 bm/B 4.12 bm/f (+/- 5.1 %) 13.94 c/B 306.64 c/f (+/- 0.8 %) 2.19 i/c 2.89 GHz doubleconversion : 146.04 MB/s (+/- 1.3 %) 6.96 Mfloat/s 50.79 i/B 1117.40 i/f (+/- 0.0 %) 0.12 bm/B 2.58 bm/f (+/- 0.5 %) 18.86 c/B 414.93 c/f (+/- 0.6 %) 2.69 i/c 2.89 GHz strtod : 126.60 MB/s (+/- 1.2 %) 6.03 Mfloat/s 51.72 i/B 1137.85 i/f (+/- 0.0 %) 0.15 bm/B 3.20 bm/f (+/- 0.2 %) 21.76 c/B 478.70 c/f (+/- 0.5 %) 2.38 i/c 2.89 GHz abseil : 363.70 MB/s (+/- 1.3 %) 17.33 Mfloat/s 26.93 i/B 592.50 i/f (+/- 0.0 %) 0.02 bm/B 0.50 bm/f (+/- 0.3 %) 7.58 c/B 166.69 c/f (+/- 0.5 %) 3.55 i/c 2.89 GHz boost spirit qi : 543.73 MB/s (+/- 1.2 %) 25.92 Mfloat/s 21.09 i/B 464.00 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 53.3 %) 5.07 c/B 111.44 c/f (+/- 0.2 %) 4.16 i/c 2.89 GHz fastfloat : 729.81 MB/s (+/- 1.7 %) 34.78 Mfloat/s 12.14 i/B 267.04 i/f (+/- 0.0 %) 0.00 bm/B 0.01 bm/f (+/- 0.5 %) 3.78 c/B 83.11 c/f (+/- 0.3 %) 3.21 i/c 2.89 GHz fastfloat fixed : 824.43 MB/s (+/- 1.3 %) 39.29 Mfloat/s 10.23 i/B 225.02 i/f (+/- 0.0 %) 0.00 bm/B 0.00 bm/f (+/- 1.3 %) 3.34 c/B 73.54 c/f (+/- 0.4 %) 3.06 i/c 2.89 GHz
fastfloatが少し落ち込んでますが、全体的な傾向は変わらず。
Qualcomm Snapdragon 720G(Redmi Note 9S) clang 11.0.0
fastfloat (fake) : 919.94 MB/s (+/- 1.7 %) 43.85 Mfloat/s netlib : 199.87 MB/s (+/- 0.7 %) 9.53 Mfloat/s doubleconversion : 119.26 MB/s (+/- 1.0 %) 5.68 Mfloat/s strtod : 29.22 MB/s (+/- 0.3 %) 1.39 Mfloat/s abseil : 241.33 MB/s (+/- 1.6 %) 11.50 Mfloat/s boost spirit qi : 321.52 MB/s (+/- 1.4 %) 15.32 Mfloat/s fastfloat : 575.66 MB/s (+/- 1.0 %) 27.44 Mfloat/s fastfloat fixed : 714.75 MB/s (+/- 1.0 %) 34.07 Mfloat/s
これもTermux上なのでperfがないっぽい。
MacOSXと同じくstrtod
が遅い。どんな実装なのか気になる…。
まとめ
全体的な速度で比較するとこんな感じでしょうか。
strtod <= doubleconversion < netlib < abseil <= boost spirit qi << fastfloat < fastfloat fixed
fastfloat圧倒的ですね。
Apache2.0ライセンスだってことが気になるぐらいで、大きな弱点も見当たらないので基本的にはfastfloat使えばいいと思いました。
昔は速いと思っていたdoubleconversionがstrtod
と対して変わらないのはちょっとびっくりでした。
ただし、std::from_charsが普通に使えるようになったり、内部の実装が大きく改善することは今後当然考えられるので、あくまで「私が使った環境に依存している」ことはご留意ください。
ベンチマークを試すのは比較的簡単なので、技術選定の際にはご自身の環境での評価をオススメします。
cmakeでHeader Onlyライブラリをお手軽に使う
FetchContentを使う動機
cmakeでHeader Onlyライブラリをちょっと使いたい場合ありますよね。
git submoduleでいいのですが、ちょっと管理が煩雑だなぁと思っています。
普段はなるべくConanパッケージを作る→使うのですが、お試しでマイナーなライブラリ使うときには仰々しいかなぁと思ったりします。
そこでCMake 3.11から導入されたFetchContent機能を利用してみました。 cmake.org
FetchContentはかなり柔軟に書くことができて、gitで指定ブランチもtar.gz/zipの取得・展開もできます。
私は「なるべくリリースされたものを使いたい」という考え方なので、リリースされたものを利用することにします。
今回はちょうど最近リリースされたCTML 2.0.0を触ってみたかったのでそれを例にしてみます。
※CTMLはHeader OnlyのHTML生成ライブラリです。
CMakeLists.txtの書き方
FetchContent機能を使ったCMakeLists.txtの例です。
project(ctml CXX) cmake_minimum_required(VERSION 3.11.0) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") include(FetchContent) FetchContent_Populate( ctml URL https://github.com/tinfoilboy/CTML/archive/2.0.0.tar.gz URL_HASH MD5=936e08167384fee9a4445fed343e83dc ) include_directories(${ctml_SOURCE_DIR}/include) add_executable(test test.cpp)
本当はFetchContent_Declare
や FetchContent_MakeAvailable
を組み合わせるべきなんでしょうが、簡単にするため FetchContent_Populate
だけですませています。
CMakeの実行と結果の確認
CMakeLists.txtの存在するディレクトリでcmakeを実行してみます。
% cmake -B build --verbose -S . -- The CXX compiler identification is AppleClang 12.0.0.12000032 -- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ -- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Populating ctml -- Configuring done -- Generating done -- Build files have been written to: /Users/toge/src/ctml/build/ctml-subbuild Scanning dependencies of target ctml-populate [ 11%] Creating directories for 'ctml-populate' [ 22%] Performing download step (download, verify and extract) for 'ctml-populate' -- Downloading... dst='/Users/toge/src/ctml/build/ctml-subbuild/ctml-populate-prefix/src/2.0.0.tar.gz' timeout='none' -- Using src='https://github.com/tinfoilboy/CTML/archive/2.0.0.tar.gz' * Closing connection 0 -- verifying file... file='/Users/toge/src/ctml/build/ctml-subbuild/ctml-populate-prefix/src/2.0.0.tar.gz' * Closing connection 1 -- Downloading... done -- extracting... src='/Users/toge/src/ctml/build/ctml-subbuild/ctml-populate-prefix/src/2.0.0.tar.gz' dst='/Users/toge/src/ctml/build/ctml-src' -- extracting... [tar xfz] -- extracting... [analysis] -- extracting... [rename] -- extracting... [clean up] -- extracting... done [ 33%] No patch step for 'ctml-populate' [ 44%] No update step for 'ctml-populate' [ 55%] No configure step for 'ctml-populate' [ 66%] No build step for 'ctml-populate' [ 77%] No install step for 'ctml-populate' [ 88%] No test step for 'ctml-populate' [100%] Completed 'ctml-populate' [100%] Built target ctml-populate -- Configuring done -- Generating done -- Build files have been written to: /Users/toge/src/ctml/build
こうすると、buildディレクトリの下が次のようになります。
% find build -type d build build/ctml-src build/ctml-src/include build/ctml-src/tests build/ctml-src/.github build/CMakeFiles build/CMakeFiles/3.15.3 build/CMakeFiles/CMakeTmp build/CMakeFiles/test01.dir build/ctml-subbuild build/ctml-subbuild/CMakeFiles build/ctml-subbuild/CMakeFiles/ctml-populate.dir build/ctml-subbuild/CMakeFiles/3.15.3 build/ctml-subbuild/ctml-populate-prefix build/ctml-subbuild/ctml-populate-prefix/tmp build/ctml-subbuild/ctml-populate-prefix/src build/ctml-subbuild/ctml-populate-prefix/src/ctml-populate-stamp build/ctml-build
ちゃんとctml-srcの下にCTML-2.0.0.tar.gzを展開した結果が配置されてます。
ソースコードでの参照方法
build/ctml-src
というパスは <name>_SOURCE_DIR
というCMakeの変数で参照可能になっています。
name
はFetchContent_Populateの最初の引数で指定しているので、今回の場合はctml
ですね。
このため、今回は次のようにinclude_directoryを指定しています。
include_directories(${ctml_SOURCE_DIR}/include)
これでソースコードの中でCTMLのヘッダをインクルードできます。
#include <iostream> #include "ctml.hpp" int main(int argc, char* argv[]) { ...
まとめ
CMakeのFetchContentを利用してHeader Onlyライブラリを利用する方法を説明しました。
FetchContentではconfigure→build→testの処理も定義できるみたいなのですが、今回はHeader Onlyライブラリなので何も指定していませんでした。
機会があればビルド必須なライブラリをFetchContentで利用してみようと思います。
C++のロギングライブラリのメモ
昨年末の話になりますが、ぼーっとredditのC++カテゴリを見ていたらlwlogというライブラリの話題が出てました。 「spdlogより速い」っていうのが売りのようです。確かに速そう。 www.reddit.com
ちょうど良いのでロギングライブラリの個人的なメモをしておこうと思います。
boost log
C++の定番ライブラリboostのloggingライブラリです。 機能豊富ですが、残念ながら大分遅いです。 C++ 的に遅いのはかなり致命的だなぁと思うのであまり積極的には使いません。 boost以外使えない場合とかあるのでそういう時に重宝します。
spdlog
多分boost logを含めた昔からあるloggingライブラリを駆逐する高速処理のロギングライブラリでした。 今となってはもっと速いライブラリが出てきてしまったので、処理速度では残念な感じです。 ただ対応している出力先が豊富なので、巨大なシステムで、ログをどこかに飛ばすみたいな用途だとspdlogは便利かもしれません。
設定ファイルでsinkの構成を定義できる guangie88/spdlog_setup というライブラリがあるのが重宝します。 github.com
quill
私としては最近一番使っているロギングライブラリになります。
- 最低限の機能がある
- 簡単に使える
- 高速に動作する
- ログファイルがテキストで扱いやすい
- Conanパッケージがある といったところが気に入ってます。
ログローテートなどはあまり凝ったことはできませんので、そういうのを求めるならspdlogなのかなぁと思います。
Nanolog
quillよりもちょっとだけ速いロギングライブラリ。 いろいろ野心的で、好奇心をくすぐられる部分も多いのですが、以下の点が個人的には大きなデメリットです。
- Linuxのみに対応
- ログファイルが独自バイナリで閲覧時に専用コマンドでテキスト化
quillよりも圧倒的に速いとかなら考えるのですが、ベンチマーク見ると10%も変わらないです。 今のところ積極的に使う理由が無いなぁと。
lwlog
期待の新星。 sinkの設定をcompile timeで構成できるのが速そう。
今のところVisual C++でのビルドが前提になってしまっているけれど、CMake対応してくれるみたいです。 手軽に使えるようになったら是非使ってみたい。
Conanで依存関係の衝突にあって対処した話
直近困ったのでメモ。
TL;DR
- Conanでパッケージ衝突がおきたら、パッケージ依存グラフ機能を使おう
- 自分でレシピを書くことがあったら、依存パッケージのバージョンはできるだけ範囲指定してほしい(願望)
OpenCVとTesseract OCRを使ってみたかった
相変わらずC++で何かをする時にはパッケージマネージャとしてConanを使っています。 OpenCVとTesseract OCRを同時に利用したい場合が出てきたんで、こんなconanfile.txtを書くことになりました。
[requires] opencv/4.5.1 tesseract/4.1.1 [generators] cmake
で、このconanfile.txtがあるディレクトリでconan install .
すると、こんなエラーが出てしまいます。
% conan install . Configuration: [settings] arch=x86_64 arch_build=x86_64 build_type=Release compiler=gcc compiler.libcxx=libstdc++11 compiler.version=10.2 os=Linux os_build=Linux [options] [build_requires] [env] WARN: libtiff/4.1.0: requirement libwebp/1.1.0 overridden by leptonica/1.79.0 to libwebp/1.0.3 ERROR: libtiff/4.1.0: Incompatible requirements obtained in different evaluations of 'requirements' Previous requirements: [zlib/1.2.11, xz_utils/5.2.5, libjpeg/9d, jbig/20160605, zstd/1.4.5, libwebp/1.1.0] New requirements: [zlib/1.2.11, xz_utils/5.2.5, libjpeg/9d, jbig/20160605, zstd/1.4.5, libwebp/1.0.3]
うーん、よく分からないですね。 OpenCVとtesseractなのに、libtiffがlibwebpのこと文句言ってるし…。
パッケージ依存グラフで原因を調べてみる
こういうのは大抵パッケージの依存パッケージの衝突が原因です。 Conanのパッケージ依存グラフ機能(experimental)を使うと図で表示されて便利だったりします。 docs.conan.io ※Graphizのインストールが必要になります。
OpenCVの依存グラフ
最初にOpenCVの依存グラフ見てみましょう。
% conan info opencv/4.5.1@ --graph=opencv.dot % dot -Tpng opencv.dot > opencv.png
OpenCV -> libtiff -> libwebpの依存が見えますね。
Tesseract OCRの依存グラフ
次はTesseract OCRの依存グラフです。
% conan info tesseract/4.1.1@ --graph=tesseract.dot % dot -Tpng tesseract.dot > tesseract.png
webpについて2つの依存経路がありますね。
そしてwebpのバージョンが1.0.3に変化しています。
なぜこんなことが起きるかというと、leptonicaのConanレシピがwebpのバージョンを1.0.3に決め打ちしているためです。
https://conan.io/center/leptonica?version=1.79.0&tab=recipe
if self.options.with_webp: self.requires("libwebp/1.0.3")
そうするとleptonicaが依存するlibtiffも空気を読んでwebpのバージョンを1.0.3に変更してしまっているんです。 一方でOpenCV側で依存しているlibtiffは特に制約がないのでレシピの通り1.1.0を使います。
https://conan.io/center/libtiff?version=4.1.0&tab=recipe
if self.options.get_safe("webp"): self.requires("libwebp/1.1.0")
これで見事に「libtiffが依存するlibwebpのバージョンが衝突する」という事態が起きてしまいました。
回避策
回避策は何個かあると思います。 私が考えついたのは以下の2つ。
- libtiffレシピのoptionでwebpを無効にする
- leptonicaレシピのoptionでwebpを無効にする
他のプロジェクトでの汎用性を考えると、libtiffを使う場合に毎回webpを無効にするオプションをつけるのは面倒くさいので、(そうしないとlbtiffのバイナリが2バージョンできてしまう)今回はleptonicaレシピのoptionでwebpを無効にすることにします。 どうせ画像の読み書きはOpenCVでやるのでleptonicaの機能が減っても困りませんし。
% conan info tesseract/4.1.1@ -o leptonica:with_webp=False --graph=tesseract_wo_webp.dot % dot -Tpng tesseract_wo_webp.dot > tesseract_wo_webp.png
ちゃんとlibwebpのバージョンが1.1.0になりました。 これならOpenCVと衝突しなくなります。
conanfile.txtに結果を反映
次のようなconanfile.txtにすることで、衝突なくinstallができるようになりました。
[requires] opencv/4.5.1 tesseract/4.1.1 [generators] cmake [options] leptonica:with_webp=False
補足: 本来はどうあるべきか?
汚い逃げ方をしてしまいましたが、本来はレシピがお行儀良く依存関係を書いてくれていればいいのになぁと思います。 Conanでは依存パッケージのバージョン番号を範囲で指定できるのでleptonicaもlibtiffも範囲指定してくれていれば良かったんですよね。
if self.options.with_webp: self.requires("libwebp/[>=1.0.3]")
もちろんバージョンによってAPIや内部動作が変わるので、細かいバージョンの組み合わせをいちいちチェックしてられないのだろうとは思います。
そう考えると今回のようなテクニックを使う場面はまだまだありそうですね。
xcodeをバージョンアップしてもapple clangがアップデートされるとは限らない
xcodeを12.3にすることができて一安心していたのですが、 apple clangが11.0のままだったことに気がつきました。 どうやらxcodeがアップデートしても、apple clangがアップデートされるわけではないみたいですね。
こちらの内容を元に以下のコマンドでアップデートしました。 python5.com
sudo rm -rf /Library/Developer/CommandLineTools xcode-select --install
めでたくapple clangが12.0になったら、今度はConanのboostパッケージがビルドできない・・・。 どうやらレシピのバグらしくて、レシピのアップデート待ちです。 github.com
xcodeはバージョンアップするといつも何か起きてしまうなぁ。
2021-01-24 追記
Conanのboostレシピが更新されて boost 1.74.0 だったらapple clangでもビルドできるようになりました。 まだboost 1.75.0は駄目みたいです。
conanfile.txtで条件分岐したい場合はconanfile.pyを使う
TL;DR
conan
パッケージに関して環境依存で細かい条件分岐をしたい場合はconanfile.txt
ではなくconanfile.py
を使いましょう。
Pythonコードでやりたい放題です。
導入というかtermuxの話
termux
面白いですね。
普段持ち歩くAndroidでほぼ完璧なLinux環境が作れてしまうワクワク感がたまりません。
最近termux
で開発環境を作るのが楽しいです。
そこで困るのがtermux
の特殊なコンパイル環境です。
最近になってclang version 11が入ってほぼ最新のC++環境が手に入るのですが、微妙にx86_64とは異なる勝手があって困ることがあります。 今日困ったけれどなんとか解決できたものがあったのでメモっておきます。
足りないヘッダファイルがあってboostがコンパイルできない
私はC++のパッケージマネージャーの一つであるConan
を利用しています。
conan.io
Termux
に限らず、色々な環境でビルドする時にライブラリのバージョンを揃えたり、マイナーなライブラリを利用する際にとても便利です。
Termux
でもConan
を使ってboost
ライブラリの最新版を使ってみようと思ったのですが、見事にハマりました。
strfmon()
を定義している monetary.h
というヘッダがないのでboost localeとboost logがビルドできないのです。
libs/locale/src/posix/numeric.cpp:26:10: fatal error: 'monetary.h' file not found #include <monetary.h> ^~~~~~~~~~~~ 1 error generated.
conanでパッケージオプションを指定する方法
boost
のCMakelists.txt
側で認識してくれていないのが謎ですが monetary.h
が無い環境なんてかなりマイナーなのでしょう。
私としてはとにかく今利用できないのが困ります。
幸いboost locale, boost logを使っていないプロジェクトが多いので、そういうプロジェクトだけはTermux
でビルドできるようにしてみたいと思いました。
boost locale, boost logだけをビルドの対象から外すのはconan
コマンドが読み込むconanfile.txt
にパッケージオプションを指定するだけです。
[requires] boost/1.75.0 [generators] cmake [options] boost.without_locale = True boost.without_log = True
monetary.hがない時だけパッケージオプションを指定したい
上の方法でとりあえずコンパイルできるのですが monetary.h
がある環境ではboost locale, boost logもビルドしてほしいのです。
そうしないと~/.conan/data/boost//1.75.0/_/_/package
の下に「全ビルド版」と「locale, log除外版」の複数のバイナリが生成されてしまってboost
の性質上やたらディスク使用量が大きいのが嫌だなぁと。(貧乏性です)
こういう環境などによる条件分岐にconanfile.txt
は対応していません。
幸いconan
のgithub
に同じような質問をしている人がいました。
github.com
なるほどconanfile.txt
と同じようにインストールするパッケージ方法としてconanfile.py
があるんですね。
知らなんだ。
docs.conan.io
conanfile.py
の中では普通にPython
が書けてしまうのでmonetary.h
の存在チェックも可能になります。
ひとまず以下のようなconanfile.py
を書いてやりたいことを実現できるようになりました。
from conans import ConanFile import subprocess class ProjectConan(ConanFile): settings = "os", "arch", "compiler" generators = "cmake" def requirements(self): self.requires("boost/1.75.0") def config_options(self): compiler = str(self.settings.compiler) # check monetary header existance if self.settings.os == "Linux" and (compiler == "gcc" or compiler == "clang"): monetary_check = subprocess.run([compiler, "-E", "-x", "c", "-"], input="#include<monetary.h>", text=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if monetary_check.returncode != 0: self.options["boost"].without_locale = True self.options["boost"].without_log = True
まとめ
termuxのclangで最適化
termuxでコンパイルする場合、 「とにかくホスト上で最適化して動けばいいんだよ」 といつも思っています。 というかC++でコンパイルする時は大抵そう思ってる、貧乏性です。 「必要になったらそのCPU用にコンパイルすればいいでしょ?」という適当な考え方です。
「termuxのclangで何を指定すればいいんだろうか?」という疑問に関するメモです。
無指定
echo | clang++ -E -v -
の結果を整形
-cc1 -triple aarch64-unknown-linux-android24 -disable-free -disable-llvm-verifier -discard-value-names -main-file-name - -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=non-leaf -fno-rounding-math -mconstructor-aliases -target-cpu generic -target-feature +neon -target-abi aapcs -mllvm -aarch64-fix-cortex-a53-835769=1 -fallow-half-arguments-and-returns -fno-split-dwarf-inlining -debugger-tuning=gdb -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fcolor-diagnostics
-target-feature +neon
が指定されているけれど、これは使われているのかなぁ。
neon勉強しないと分からない。
-O3
いつものやつ。
echo | clang++ -E -v -O3 -
を整形。
-cc1 -triple aarch64-unknown-linux-android24 -disable-free -disable-llvm-verifier -discard-value-names -main-file-name - -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=non-leaf -fno-rounding-math -mconstructor-aliases -target-cpu generic -target-feature +neon -target-abi aapcs -mllvm -aarch64-fix-cortex-a53-835769=1 -fallow-half-arguments-and-returns -fno-split-dwarf-inlining -debugger-tuning=gdb -O3 -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp
うん、いつものO3な気がする。
-O3 -march=natvie
> echo | clang++ -E -v -O3 -march=native - clang version 11.0.0 (https://github.com/termux/termux-packages 39dec01e591687a324c84205de6c9713165c4802) Target: aarch64-unknown-linux-android24 Thread model: posix InstalledDir: /data/data/com.termux/files/usr/bin clang-11: error: the clang compiler does not support '-march=native'
x86_64と違って-march=natvieという指定に対応していない・・・。
-O3 -mtune=native
echo | clang++ -E -v -O3 -mtune=native -
を整形。
-cc1 -triple aarch64-unknown-linux-android24 -disable-free -disable-llvm-verifier -discard-value-names -main-file-name - -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=non-leaf -fno-rounding-math -mconstructor-aliases -target-cpu generic -target-feature +neon -target-abi aapcs -mllvm -aarch64-fix-cortex-a53-835769=1 -fallow-half-arguments-and-returns -fno-split-dwarf-inlining -debugger-tuning=gdb -O3 -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp
まだmtuneが生きている。だけど-O3
と変わらない?
-O3 -mcpu=native
echo | clang++ -E -v -O3 -mtune=native -
を整形。
-cc1 -triple aarch64-unknown-linux-android24 -disable-free -disable-llvm-verifier -discard-value-names -main-file-name - -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=non-leaf -fno-rounding-math -mconstructor-aliases -target-cpu cortex-a73 -target-feature +fp-armv8 -target-feature +neon -target-feature +crc -target-feature +crypto -target-feature +sha2 -target-feature +aes -target-abi aapcs -mllvm -aarch64-fix-cortex-a53-835769=1 -fallow-half-arguments-and-returns -fno-split-dwarf-inlining -debugger-tuning=gdb -O3 -ferror-limit 19 -fno-signed-char -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp
target-cpuがcortex-a73になり、target-featureに色々ついてますね。
この記事がまだ有効みたいで、x86_64ではもう使われない-mcpuがarmでは有効と。 stackoverflow.com
結論?
aarch64でclang使う時は-mcpu=nativeつけておけば良さそう。
既存のPDFをPoDoFoで変換
ふとしたことでそこらへんに転がっているPDFを加工したいことがあります。
PDFを生成するライブラリではなくて、PDFのパーザが必要になるのですがC++でやるには何がいいんですかね〜。
今回は、ざっと探して最初に見つかったPoDoFoを使おうと思ってます。
PoDoFo
ライセンスがLGPLv2なのがちょっと気に入りませんが、ちょっとしたツールを作るだけなので問題ありません。
2014年で開発止まっているようですが、今のところ問題なく使えます。
cmakeを使っているのも好印象。
命名規則がかなりごちゃごちゃなのがちょっと気になりますが。
(GetPageCount()だったり、GetNumAnnots()だったりするんです。)
残念ながらサンプルはPDFの生成しか無いし、ドキュメントはDoxygenでのクラスリファレンスなのでぱっと見使い方が分かりません。
PoDoFo ToolsとしていろいろPoDoFoライブラリを使ったツールが公開されているので、これを触りながら使い方を理解してます。
http://podofo.sourceforge.net/tools.html#tools
どうやら、PDFの編集をする一番楽な方法は全部メモリに読み込んで、編集して、書き出す方法のようなので、これに習おうと思います。
ちゃんと理解すればStream処理もできるのかもしれませんが、とりあえず後回し。
奇数頁だけ削除して書き出すプログラムを書いてみました。
#include <iostream> #include <podofo/podofo.h> int main(int argc, char* argv[]) { if (argc < 3) { std::cout << "usage : " << argv[0] << " <input file> <output file>" << std::endl; return 1; } PoDoFo::PdfMemDocument pdfdoc(argv[1]); int page_number = pdfdoc.GetPageCount(); for (int index = page_number - 1; index >= 0; --index) { if (index % 2 == 0) { continue; } pdfdoc.DeletePages(index, 1); } pdfdoc.Write(argv[2]); return 0; }
PdfMemDocumentというクラスを使うのがキモですね。
ファイル名を渡せばPDFの構造としてメモリの読み込んでくれます。
DeletePagesでメモリ上の情報から指定ページだけ決して、最後にWriteでファイル書き出し。
案外簡単。
PDFによっては次のようなメッセージがでちゃいます。
消したページを参照している処が問題起こしてるのかな。
これもおいおい対応できるようにしたいですね。
WARNING: Treating object 110 0 R as a free object.
まあ、この程度ならプログラム書かなくてもできちゃうので、もう少し中身を弄れるようになりたいところです。
最近のC++に追い付こうと思います
C++で書きなぐるがの楽しかったのですが、そろそろ限界を感じてきたので、C++14とかをちゃんと勉強しようと思ってます。
私はC++98の知識しかないので、ここ2、3年のドラスティックな変化に全然対応できないと痛感してます。
ひとまず以下をサクっと読んで文法的には理解したので、色々調べながら追いつくしかないですね。
www.apress.com
シンプルな構成のC++のHTTPクライアントライブラリは無いものか
「libcurl使えば一発じゃん」と言われればそれまでなのですが、
C++界隈の一部ではheader only libraryが大人気なわけです。
Boostやfollyみたいなガチなものから、もっとホビーなものまで沢山あるのに、
案外無いのがHTTPクライアント。
C++バリバリな方々はそもそもクライアントサイドは作らないのかな。
もう10年近く前の記事で取り上げたHappyHTTPを未だに使ってます。
toge.hatenablog.com
あの当時はまだ開発続けてくれると思ってたんだがなぁ。
全く進化なし。
取り敢えずcallbackの仕組みとかが古臭いのでlambda使ったり、enum class使ったりちょいちょい弄ってますが、
C++11/14の機能を巧みに使った素敵なライブラリを誰か作ってはいないものか。
C++ Requestsとか相当いい線行ってるんですけどね、libcurl依存なのよね。
github.com
Linuxでrarを解凍
Dockerで簡単にElasticsearch+Kibana環境
自分用のメモ。
ちゃんとやるにはpluginとか色々必要ですし、ネットワークも適切な設定が必要ですが、
まあとりあえず開発で最新版を使いたい時用手順です。
環境はFedora23 workstationです。
$ docker run -d -p 9200:9200 -p 9300:9300 -v "$PWD/esdata":/usr/share/elasticsearch/data elasticsearch:2
$ curl -XGET http://localhost:9200/ { "name" : "Runner", "cluster_name" : "elasticsearch", "version" : { "number" : "2.1.1", "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71", "build_timestamp" : "2015-12-15T13:05:55Z", "build_snapshot" : false, "lucene_version" : "5.3.1" }, "tagline" : "You Know, for Search" }
なんか立ち上がったっぽいですね。
$ tree $PWD/esdata esdata └── elasticsearch └── nodes └── 0 ├── _state │ └── global-2.st ├── indices └── node.lock 5 directories, 2 files
カレントディレクトリの下にesdataディレクトリができて、結構デカイデータが入るので注意。
$ docker ps 0c4e128e4145 elasticsearch:2 "/docker-entrypoint.s" 19 minutes ago Up 19 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp stoic_panini
とりあえず立ち上がってるね。よしよし。
次はKibanaだ。
$ docker run -e ELASTICSEARCH_URL=http://192.168.1.101:9200 -p 5601:5601 -d kibana
"192.168.1.101"はホストのIPアドレスを指定。
localhostだとKibanaのコンテナ自信のポートになっちゃうから注意。
$ curl -X GET http://localhost:5601/ <script>var hashRoute = '/app/kibana'; var defaultRoute = '/app/kibana'; var hash = window.location.hash; if (hash.length) { window.location = hashRoute + hash; } else { window.location = defaultRoute;
とりあえず動いてるっぽいですね。
後でブラウザから確認しましたが4.3.1が入ってました。(2016/1/12時点)
$ docker ps c41219f376f4 kibana "/docker-entrypoint.s" 5 minutes ago Up 5 minutes 0.0.0.0:5601->5601/tcp kibana-test02 0c4e128e4145 elasticsearch:2 "/docker-entrypoint.s" 19 minutes ago Up 19 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp stoic_panini
ポートもばっちり。
http://localhost:5601/にアクセスするとちゃんとKibanaの画面がでてきて、満足。
volumioはcronが無効になってる
Raspberry Pi 2のディストリビューションをRaspbianからVolumioにしたんだけど、
cronがことごとく動いていなかった。
ポリシーで無効になってるのか。
www.raspyfi.com
chkconfig --add cron
service cron start
でちゃんと動くようになりました。
ぐぬぬ、1週間ぐらいデータが欠損しちゃったよ。