toge's diary

コンピュータ関連の趣味をつらつらと。

「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への変換速度を計測してくれます。

このベンチマークを実行するだけだと芸がないので、以下のベンチマークも追加しています。

  • 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 

f:id:toge:20210131015608p:plain

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 

f:id:toge:20210131020058p:plain

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 

f:id:toge:20210131020251p:plain

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

f:id:toge:20210131020419p:plain

これも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_DeclareFetchContent_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++のロギングライブラリのメモ

昨年末の話になりますが、ぼーっとredditC++カテゴリを見ていたらlwlogというライブラリの話題が出てました。 「spdlogより速い」っていうのが売りのようです。確かに速そう。 www.reddit.com

ちょうど良いのでロギングライブラリの個人的なメモをしておこうと思います。

boost log

www.boost.org

C++の定番ライブラリboostのloggingライブラリです。 機能豊富ですが、残念ながら大分遅いです。 C++ 的に遅いのはかなり致命的だなぁと思うのであまり積極的には使いません。 boost以外使えない場合とかあるのでそういう時に重宝します。

spdlog

github.com

多分boost logを含めた昔からあるloggingライブラリを駆逐する高速処理のロギングライブラリでした。 今となってはもっと速いライブラリが出てきてしまったので、処理速度では残念な感じです。 ただ対応している出力先が豊富なので、巨大なシステムで、ログをどこかに飛ばすみたいな用途だとspdlogは便利かもしれません。

設定ファイルでsinkの構成を定義できる guangie88/spdlog_setup というライブラリがあるのが重宝します。 github.com

quill

github.com

私としては最近一番使っているロギングライブラリになります。

  • 最低限の機能がある
  • 簡単に使える
  • 高速に動作する
  • ログファイルがテキストで扱いやすい
  • Conanパッケージがある といったところが気に入ってます。

ログローテートなどはあまり凝ったことはできませんので、そういうのを求めるならspdlogなのかなぁと思います。

Nanolog

github.com

quillよりもちょっとだけ速いロギングライブラリ。 いろいろ野心的で、好奇心をくすぐられる部分も多いのですが、以下の点が個人的には大きなデメリットです。

  • Linuxのみに対応
  • ログファイルが独自バイナリで閲覧時に専用コマンドでテキスト化

quillよりも圧倒的に速いとかなら考えるのですが、ベンチマーク見ると10%も変わらないです。 今のところ積極的に使う理由が無いなぁと。

lwlog

github.com

期待の新星。 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   

f:id:toge:20210116024447p:plain
OpenCVの依存グラフ

OpenCV -> libtiff -> libwebpの依存が見えますね。

Tesseract OCRの依存グラフ

次はTesseract OCRの依存グラフです。

% conan info tesseract/4.1.1@ --graph=tesseract.dot    
% dot -Tpng tesseract.dot > tesseract.png   

f:id:toge:20210116024733p:plain
Tesseract OCRの依存グラフ

webpについて2つの依存経路がありますね。

  • Tesseract OCR -> leptonica -> libtiff -> libwebp
  • Tessearct OCR -> leptonica -> libwebp

そして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   

f:id:toge:20210116030821p:plain
webpを無効化したTesseract OCRの依存グラフ

ちゃんと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でパッケージオプションを指定する方法

boostCMakelists.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は対応していません。 幸いconangithubに同じような質問をしている人がいました。 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

まとめ

  • ConanでのC++パッケージ管理便利です。
  • conanfile.txtでパッケージ定義が基本ですが、条件分岐したければconanfile.pyPythonコードを書けます。

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つけておけば良さそう。

まだこのブログ残ってたんだ

もう4年以上放置されているブログなのに、はてなさんまだ保管してくれてたんですね。
ありがたや。

また少し書こうかなぁと。

まずは機能が大分変わってる気がするからそこらへん追いつかないとなぁ。

既存の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を解凍

忘れた頃に必要になるrarファイル。
ちょっと変なフォーマットだと、Gnomeの機能では解凍できないのよね。

昔は重宝したunrarなんてコマンドは最近のLinux(Fedora 23)にはなかったので、
野良ビルドすることに。

UnRar-5.3.9

ここらへんにあるとおり実行して、
できたunrarを適当に配置すればあっさり使えます。

$ tar xvf unrarsrc-5.3.8.tar.gz 
$ cd unrar
$ make
$ cp unrar /usr/local/bin

最新のフォーマットにも対応しているし、にほんご入りファイル名を処理できました。
めでたしめでたし。

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週間ぐらいデータが欠損しちゃったよ。