Yabu.log

日々の話題やIT的な雑記を書きなぐるブログ

「ふつうのLinuxプログラミング 第2版」を読んだ

本書はシステムコールとlibcのAPIなどを使ったLinuxで動作するコマンドを作るを通して、 Linuxについて深く知ろうという趣旨の本です。

読んだきっかけ

本書を読んだきっかけは自分のlinux力があまりにも低すぎると感じたため。 具体的に言うと、cmakeに-CMAKE-INSTALL_PREFIXをつけずにビルドして、/usr/localに実行バイナリをおいてしまった。 この件に関して「これじゃぁ他のユーザーに影響でちゃうよ。」と怒られたが、

  • Linuxのフォルダ構成について何も理解していない。
  • -CMAKE-INSTALL_PREFIXをつけるのはかなり基本的なことっぽい。

の2点からググってネット上のコマンドをコピペするしか能のない自分のLinux力を猛省したところ、本書を見つけた。

  • TLの強めのエンジニアがリコメンドしている
  • 最近改版された。
  • 自分が分からなかったフォルダ構成について書かれている
  • 会社に1版に大量の付箋がついているものが存在している(おそらくこれで猛勉強した同僚がいる(いた?))

の4点から、本書を学習してみることにした。

雑感

include<stdio.h>を呪文とすることなく順序立てて初心者にも理解できるように解説している。

  • 1.システムコールやそのAPIのmanコマンド読み方
  • 2.実際にシステムコールなどを使ってみる小さなコードを作る
  • 3.システムコールだけで実現できない動作をバッファなどを実装して自前で作る
  • 4.3で作った相当のものがライブラリとして用意されていることをmanコマンドで説明する

という流れでstdioのprintfの解説を試みている。本や大学の授業などでこの部分の解説を受けたことがなかったので、 ここの流れは見事だなと思った。

manコマンドのセクションについて

manコマンドで実行コマンドまたはライブラリの関数等の使い方を調べることができます。

調べる対象のコマンド名のほかにもセクション番号を引数で渡すことができます。 引数としてなにがあり、どの数字に対応しているかはman manで調べることができます。

$man man
・・・(略)
       1   Executable programs or shell commands
       2   System calls (functions provided by the kernel)
       3   Library calls (functions within program libraries)
       4   Special files (usually found in /dev)
       5   File formats and conventions eg /etc/passwd
       6   Games
       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
       8   System administration commands (usually only for root)
       9   Kernel routines [Non standard]
・・・(略)

名前がかぶっているもの、例えばprintfなどはstdioで定義されている関数以外にもユーザーコマンドとして存在しているため、manコマンドで調べると、ライブラリの関数よりも先にユーザーコマンドのセクションがヒットして表示されてしまいます。

$man printf

PRINTF(1)  User Commands  PRINTF(1)

NAME
       printf - format and print data

ライブラリの関数を調べたいときはman 3 prinftと引数にセクション番号を渡すことで表示できます。

$man 3 printf

PRINTF(3)  Linux Programmer's Manual  PRINTF(3)

NAME
       printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf - formatted output conversion

SYNOPSIS
       #include <stdio.h>

       int printf(const char *format, ...);
・・・(略)

本書ではライブラリ関数やユーザーコマンドを見分けるため、printf(1)やprintf(3)といった書き方に統一しています。 これは分かりやすく、ほかの本でも見かける*1ので、知らない人は覚えておくとよいと思います。

本書を読むまではmanはコマンドの意味を調べるもの、という理解でしたが、実際は更に協力なものでした。 ちょっとTIPS的な要素ですが、man asciiと打つとアスキーコード表が出てきます。

Linux世界を構成する3つの概念とは

Linux世界はこの3つの概念によって成立しています。つまりLinuxプログラミングを理解するためにはこの3つの概念をよく理解しなければならないということです。そしてそれことが本書の目的です。*2

ストリームとは

ファイルディスクリプタで表現され、read()またはwrite()で操作できるもののこと(p82)

  • STDIO,STDOUT
  • ネットワーク
  • ファイル

などを抽象化したインターフェースのことです。

ファイルディスクリプタとは

OSがストリームに対して割り当てる番号(int)のこと。 この割り当てられたファイルディスクリプタに対して、read(2),wreite(2)を使って入出力を制御します。 read(2)やwrite(2)は引数として操作対象としてフアィルディスクリプタを引数に取ります。

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);
       ssize_t write(int fd, const void *buf, size_t count);

open(2)でストリームを作成し、ディスクリプタを得る。close(2)でストリームを破棄する。

stdioとFILE型

C言語にはファイルディスクリプタをラップし、内部でバッファ管理している標準ライブラリがあります。 それがstdioです。

       #include <stdio.h>

       size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

       size_t fwrite(const void *ptr, size_t size, size_t nmemb,
                     FILE *stream);

unistd.hのread(2),write(2)等はUNIXシステムコールであり、なるべく移植性が高いstdioの関数を使うように本書に記されています。

read()やwrite()は基本的にはUNIXシステムコールであり、ほかのOSでも存在するとは限りません。しかし、fread()やfwrite()はC言語の標準関数ですから、より多くの環境で使えることが期待できるのです。(p121)

stdioではファイルディスクリプタではなく、それをラップしたFile型を使います。

File型からファイルディスクリプタを得るにはfileno(3)を使います。逆にファイルディスクリプタからFile型を作るにはfdopen型を使います。

以下のプログラムではSTDOUTにはファイルディスクリプタ=1がデフォルトで割り当てられていますが、こちらをラップしたFILE型を作ってfwriteで書き込みを行っています。

#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main(){
                 FILE *stdout_file = fdopen(STDOUT_FILENO,"r+");
                 char *hello = "hello world\n";
                 fwrite(hello,strlen(hello),1,stdout_file);
}

ヘッダファイルは必要になったときに書けば良い。無理して関数とヘッダファイルの対応は覚えなくてOK

大学時代はstdlibはとりあえず書いとけ、みたいな雑な教え方をされた覚えがある。 プログラムを写経して覚えるスタイルだと、そんなかんじで最初に書くことになるのがヘッダファイルのインクルードになるが、 これから各プログラムに何が必要で、それを予め書いておくというプログラミングスタイルに固執する必要はなさそうだと思いました。

manを見れば必要なヘッダファイルはかいてありますから、コードを書いていて新しいAPIが必要になった時点でmanを参照すればいいのです。関数とヘッダファイルの対応をう躍起になって覚える必要はありません。記憶力はもっと有意義なところに使いましょう(p93)

Linuxやlibcに足りないものが沢山見えてくる。

printf()が濫用される理由

これはちょっとした雑学ですが。

printf()はフォーマット付きの出力用のAPIなのですから、 HelloWorldプログラムのようにフォーマット機能が必要ないときに、printf()を使う必然性はないはずです。それでも何かとprintf()が使われています。このようにprintf()が乱用されるのは「標準出力に文字列をそのまま出力するAPIがstdioにないことが原因でしょう。fputs()だと標準入出力を明示的に指定しないといけませんし、puts()だけだと改行が内化されるのが邪魔です。(p118より)

コマンドライン引数のフォーマットに統一性がない。

古いunixのコマンドなどは前述のgetopt(3)を使わずに自前で引数をパースしているものも多いようです。 そのようなツールでは一般的なコマンドライン引数の扱いとは違ったルールで動作している可能性があります。 本書では今linuxツールを作るならgetoptを利用することを推奨しています。

例外処理の仕組み

これは本書に書かれていることではありませんが、自分の感触です。

erronoを使った例外処理なんかはAPI自体がグローバル変数を強要してくる作りになっていて、あまり綺麗なAPIになっていないと思いました。*3

ディレクトリツリーの標準規格FHS(The FilesystemHierarchy Standard)

読む動機となった/usr/localなどのフォルダ役割についてのまとめ。 ディレクトリツリーの標準規格から、主要なディレクトリとその用途。主に9章の内容をまとめたもの

/                   # ルートディレクトリ。bin,etc,dev,proc,sysあたりがまともな動作に必須
├── bin           # ブートするときに必要なコマンド
├── boot          # カーネルのプログラム
├── dev           # デバイスファイル
├── etc           # マシンごとの設定ファイル
├── lib           # ライブラリ置き場(ディストリビューションが管理)
├── proc          # プロセスファイル
├── sbin          # (ブート時も必要となる)管理者用コマンド
├── sys           # デバイスやデバイスドライバの情報 
├── tmp           # 一時ファイルやリカバリファイル
├── usr           # NFSで共有する前提のものを置く。(単ノード利用前提は/var配下に)
│   ├── bin      # コマンド(ディストリビューションが管理)
│   ├── include  # システムのヘッダファイル    
│   ├── local    # ユーザーが管理しても良い。(/usr直下はディストリビューション管理)
│   │   └── bin # コマンド(ユーザーが管理)
│   ├── sbin     # 管理者用コマンド
│   ├── share    # ドキュメント。(manやinfo)
│   │   └── man # manページ。roff形式
│   └── src      # linuxカーネルのソースなどのシステムが使うコード置き場
└── var           # 頻繁に書き換えるファイル・複数マシンで共有しないファイル等
    ├── log       # サーバープロセスのログ
    ├── spool     # メールやプリンタの入力
    └── run       # 起動中のサーバープロセスのPIDファイル
    └── tmp       # 一時ファイルやリカバリファイル

感想

本書はC言語文法の解説などはほとんど書かれておらず、全くの初心者向け、というわけではありませんし、 ライブラリの中の実装などを詳しく説明するなどもないため、上級者向けというわけでもありません。 自分のようにOSやC言語の本を適当に読み漁り、中途半端に知識はついているが基礎が出来ていない人にちょうどいいのかなと思います。 本書は割と細かく丁寧に書かれており、printfに辿り着くまでに100ページ以上あるので、それが我慢できない人にはおすすめしません。

あたりにおすすめです。

というわけで全く新しく知ること、はあまり書かれていませんでしたが、過去の経験の蓄積からなんとなく、そうなんだ、こういうふうになっているんだ、と思い込んでいた事象について、はっきりと仕組みの解説などが書かれていて、いい加減な知識を補強する土台のようなものが出来、安心することができました。個人的にはmanコマンドを引く癖がついたのが一番でかい収穫だと思います。

Linuxのユーザーとしてはまだまだ知識が足りない気がする。LPICの勉強でもすればいいのかな。

次に読む本

シェルスクリプト書けないとやっていけないよ、とのことなので

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

入門UNIXシェルプログラミング―シェルの基礎から学ぶUNIXの世界

*1:紹介システムパフォーマンスとか

*2:p5の1.1本書の概要より引用

*3:この仕組みはマルチスレッド化した際に利用できなくなるケースがあるらしい

time(2)を使って手軽に実行時間、メモリ最大使用量、CPU利用率を知りたい

time(2)コマンドには2種類あります。

  • bash組み込みのtimeコマンド
  • gnuのtime

gnuのtimeコマンドはcpu使用率や平均メモリ利用量、最大メモリ使用量などを計測することができます。 デフォルトの出力フォーマットが見にくいですが、利用者が独自フォーマットを定義して必要な情報のみを選択する事ができます。

FORMATTING THE OUTPUT

% A literal `%'.
C Name and command line arguments of the command being timed.
D Average size of the process's unshared data area, in Kilobytes.
E Elapsed real (wall clock) time used by the process, in [hours:]minutes:seconds.
F Number of major, or I/O-requiring, page faults that occurred while the process was running. These are faults where the page has actually migrated out of primary memory.
I Number of file system inputs by the process.
K Average total (data+stack+text) memory use of the process, in Kilobytes.
M Maximum resident set size of the process during its lifetime, in Kilobytes.
O Number of file system outputs by the process.
P Percentage of the CPU that this job got. This is just user + system times divided by the total running time. It also prints a percentage sign.
R Number of minor, or recoverable, page faults. These are pages that are not valid (so they fault) but which have not yet been claimed by other virtual pages. Thus the data in the page is still valid but the system tables must be updated.
S Total number of CPU-seconds used by the system on behalf of the process (in kernel mode), in seconds.
U Total number of CPU-seconds that the process used directly (in user mode), in seconds.
W Number of times the process was swapped out of main memory.
X Average amount of shared text in the process, in Kilobytes.
Z System's page size, in bytes. This is a per-system constant, but varies between systems.
c Number of times the process was context-switched involuntarily (because the time slice expired).
e Elapsed real (wall clock) time used by the process, in seconds.
k Number of signals delivered to the process.
p Average unshared stack size of the process, in Kilobytes.
r Number of socket messages received by the process.
s Number of socket messages sent by the process.
t Average resident set size of the process, in Kilobytes.
w Number of times that the program was context-switched voluntarily, for instance while waiting for an I/O operation to complete.
x Exit status of the command.

(man 2 timeから抜粋)

環境変数の$TIMEにフォーマットを登録するか-f optionでフォーマットを指定することができます。

export TIME="time result\ncmd:%C\nreal %es\nuser %Us \nsys  %Ss \nmemory:%MKB \ncpu %P"

もしくは

time -f "time result\ncmd:%C\nreal %es\nuser %Us \nsys  %Ss \nmemory:%MKB \ncpu %P" <command>

実行結果(sleep コマンドを実行しています)

$ /usr/bin/time sleep 1
time result
cmd:sleep 1
real 1.00s
user 0.00s 
sys  0.00s 
memory:2128KB 
cpu 0%

gdbを使ってsegmentation faultを調べる

いきなりsegumentation fault (core dump)と端末に表示されて異常終了をするプログラムに頭を悩まされていました。

...(順調に処理中)...
/home/yuyabu/3d/bin/02.sh: 42 行: 58161 浮動小数点例外   (コアダンプ) openMVG/Linux-x86_64-RELEASE/openMVG_main_IncrementalSfM -i result/sfm_data.json -m matches -o out

C/C++Linuxな環境であり、かつ自分で作ったファイルまたはオープンソースなどでバイナリに対応したファイルが用意できれば、gdbを使ってエラーが発生している箇所がわかるかもしれません。

ちなみに今回の対象ソースはOpenMVGという3Dを処理するOSSです。

1.core fileが作成されるように設定する

Unix系のOSではプログラムが異常終了したときにメモリなどの情報を「corefile」として吐き出す機能があります。 こちらはデフォルトの状態では出力サイズが制限されており作成されないようになっているので、ulimit -c unlimitedで corefileを無制限に作成できるように設定します。

 ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 4124107
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4124107
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

これでcoredump発生時に実行時のカレントディレクトリにcorefileが作成されるようになります。

2.セグメンテーションフォルトが発生するプログラムをDEBUGビルドする

エラーが発生しているバイナリのソースをデバッグビルドします デバッグビルドができるようなオプションをつけてビルドツールを実行してください。 こちらは調査対象のアプリやビルドツールによって色々なので各自調べてください。

今回私が調査したopenMVGの場合は以下ビルド方法の「3.Configure and build」のオプション-DCMAKE_BUILD_TYPEをRELEASEからDEBUGに変えるだけです。

github.com

$ cmake -DCMAKE_BUILD_TYPE=DEBUG ../openMVG/src/
$ cmake --build . --target install

3.gdbで調べる

gdb <実行バイナリ> <corefile>
gdb ../build/openMVG/Linux-x86_64-DEBUG/openMVG_main_IncrementalSfM ./core 
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ../build/openMVG/Linux-x86_64-DEBUG/openMVG_main_IncrementalSfM...
done.

warning: core file may not match specified executable file.
[New LWP 58245]
...(略)...
[New LWP 58268]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `/home/yuyabu/3d/build/openMVG/Linux-x86_64-DEBUG/openMVG_main_IncrementalSf'.
Program terminated with signal SIGFPE, Arithmetic exception.
#0  0x000055dc1f18c7e6 in Eigen::internal::gebp_kernel<double, double, long, Eigen::internal::blas_data_mapper<double, long, 0, 0>, 12, 4, false, false>::operator() (this=0x7f2af88ccf0c, res=..., blockA=0x7f2af88cd340, blockB=0x7f2af88cd3a0, rows=3, depth=0, cols=3, alpha=-1, strideA=0, strideB=0, offsetA=0, offsetB=0)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h:926
926       const Index actual_panel_rows = (3*LhsProgress) * std::max<Index>(1,( (l1 - sizeof(ResScalar)*mr*nr - depth*nr*sizeof(RhsScalar)) / (depth * sizeof(LhsScalar) * 3*LhsProgress) ));
[Current thread is 1 (Thread 0x7f2af88ce700 (LWP 58245))]
(gdb) 

backtraceを取って該当の処理がどこから呼ばれたのか?ということもある程度わかります。

(gdb) bt
#0  0x000055dc1f18c7e6 in Eigen::internal::gebp_kernel<double, double, long, Eigen::internal::blas_data_mapper<double, long, 0, 0>, 12, 4, false, false>::operator() (this=0x7f2af88ccf0c, res=..., blockA=0x7f2af88cd340, blockB=0x7f2af88cd3a0, rows=3, depth=0, cols=3, alpha=-1, strideA=0, strideB=0, offsetA=0, offsetB=0)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h:926
#1  0x000055dc1f2ef74a in Eigen::internal::triangular_solve_matrix<double, long, 2, 2, false, 1, 0>::run (size=3, otherSize=3, _tri=0x7f2af88cdb30, triStride=3, _other=0x7f2af88cdd00, otherStride=3, blocking=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/products/TriangularSolverMatrix.h:324
#2  0x000055dc1f3214ef in Eigen::internal::triangular_solve_matrix<double, long, 1, 1, false, 0, 1>::run (size=3, cols=3, tri=0x7f2af88cdb30, triStride=3, _other=0x7f2af88cdd00, otherStride=3, blocking=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/products/TriangularSolverMatrix.h:32
#3  0x000055dc1f36ef5a in Eigen::internal::triangular_solver_selector<Eigen::Transpose<Eigen::Matrix<double, 3, 3, 1, 3, 3> const> const, Eigen::Matrix<double, 3, 3, 1, 3, 3>, 1, 1, 0, -1>::run (lhs=..., rhs=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/SolveTriangular.h:102
#4  0x000055dc1f36cf13 in Eigen::TriangularViewImpl<Eigen::Transpose<Eigen::Matrix<double, 3, 3, 1, 3, 3> const> const, 1u, Eigen::Dense>::solveInPlace<1, Eigen::Matrix<double, 3, 3, 1, 3, 3> > (this=0x7f2af88cd4e0, _other=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/SolveTriangular.h:179
#5  0x000055dc1f36ad71 in Eigen::TriangularViewImpl<Eigen::Transpose<Eigen::Matrix<double, 3, 3, 1, 3, 3> const> const, 1u, Eigen::Dense>::solveInPlace<Eigen::Matrix<double, 3, 3, 1, 3, 3> > (this=0x7f2af88cd4e0, other=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/TriangularMatrix.h:511
#6  0x000055dc1f368157 in Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>::solveInPlace<Eigen::Matrix<double, 3, 3, 1, 3, 3> > (this=0x7f2af88cdb30, bAndX=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Cholesky/LLT.h:496
#7  0x000055dc1f366674 in Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>::_solve_impl<Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> >, Eigen::Matrix<double, 3, 3, 1, 3, 3> > (this=0x7f2af88cdb30, rhs=..., dst=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Cholesky/LLT.h:476
#8  0x000055dc1f3653de in Eigen::internal::Assignment<Eigen::Matrix<double, 3, 3, 1, 3, 3>, Eigen::Solve<Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>, Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> > >, Eigen::internal::assign_op<double, double>, Eigen::internal::Dense2Dense, void>::run (dst=..., src=...)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/Solve.h:147
#9  0x000055dc1f364a1d in Eigen::internal::call_assignment_no_alias<Eigen::Matrix<double, 3, 3, 1, 3, 3>, Eigen::Solve<Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>, Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> > >, Eigen::internal::assign_op<double, double> > (dst=..., src=..., func=...)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/AssignEvaluator.h:836
#10 0x000055dc1f363a90 in Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 3, 1, 3, 3> >::_set_noalias<Eigen::Solve<Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>, Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> > > > (this=0x7f2af88cdd00, other=...)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/PlainObjectBase.h:728
#11 0x000055dc1f362cbd in Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 3, 1, 3, 3> >::PlainObjectBase<Eigen::Solve<Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>, Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> > > > (this=0x7f2af88cdd00, other=...)
    at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/PlainObjectBase.h:537
#12 0x000055dc1f361f70 in Eigen::Matrix<double, 3, 3, 1, 3, 3>::Matrix<Eigen::Solve<Eigen::LLT<Eigen::Matrix<double, 3, 3, 1, 3, 3>, 2>, Eigen::CwiseNullaryOp<Eigen::internal::scalar_identity_op<double>, Eigen::Matrix<double, -1, -1, 1, -1, -1> > > > (this=0x7f2af88cdd00, other=...) at /home/yuyabu/3d/src/openMVG/src/third_party/eigen/Eigen/src/Core/Matrix.h:379
#13 0x000055dc1f360df1 in ceres::internal::InvertPSDMatrix<3> (assume_full_rank=true, m=...) at /home/yuyabu/3d/src/openMVG/src/third_party/ceres-solver/internal/ceres/invert_psd_matrix.h:60
#14 0x000055dc1f389aaf in ceres::internal::SchurEliminator<2, 3, 6>::Eliminate (this=0x7f2af88cdcb0, A=0x7f2af88cdd60, b=0x55dc208c71f0, D=0x55dc20595f50, lhs=0x7f2af88cdb40, 
    rhs=0x55dc1f38b360 <ceres::internal::SchurEliminator<2, 3, 6>::ChunkDiagonalBlockAndGradient(ceres::internal::SchurEliminator<2, 3, 6>::Chunk const&, ceres::internal::BlockSparseMatrix const*, double const*, int, Eigen::Matrix<double, 3, 3, 1, 3, 3>*, double*, double*, ceres::internal::BlockRandomAccessMatrix*)+552>)
    at /home/yuyabu/3d/src/openMVG/src/third_party/ceres-solver/internal/ceres/schur_eliminator_impl.h:273

参考: qiita.com

なおデバッグビルドしたバイナリがなくても-cオプションでcorefileだけでgdbを起動できます。

$man gdb
...(略)
OPTIONS
...(略)
       -core=file
       -c file
           Use file file as a core dump to examine.

ただし関数名などがわからないのでアドレスみたらわかるマンじゃないときついと思います。

$ gdb -c core
・・・(略)
[New LWP 58268]
Core was generated by `/home/yuyabu/3d/build/openMVG/Linux-x86_64-DEBUG/openMVG_main_IncrementalSf'.
Program terminated with signal SIGFPE, Arithmetic exception.
#0  0x000055dc1f18c7e6 in ?? ()
[Current thread is 1 (LWP 58245)]

約2年間続いた読書会で「プログラマのためのSQL」を読み終わりました

本日がラスト39回目で最終回。翻訳者のミックさんが参加され翻訳者後書きを読んでもらえました。非常に感慨深いものがありました。

初回が2017年3/1で二年と1ヶ月、全39回で読み終わりました。私は15回から参加しています。ちなみに私は目黒バイナリ勉強会で参加数2位のようです。(一位は@quwaharaさん)

meguro-binary-study.connpass.com

会場は中目黒でした。寒いためか花見客が少なかった?そうです。

打ち上げ。シリコンバレーのエンジニア事情などの話題で盛り上がりました。

本書について

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

本書はSQLをメインテーマに書かれた和書で最も分厚いと思います(多分)。

本書に掲載されているクエリからはこんなこともできるのか!と感じさせるほどSQLの威力が存分に発揮されていますが、 一方でSQLの利用はデータ取り出しにとどめておいて、複雑なことは専用のミドルウェアやアプリでやるべきという主張も読み取れます。 普段書かないようなクエリを通してSQLとは何か?に迫る本書はSQLの入門マニュアルでも百科事典でもなく、SQLのエッセンスに迫れる数少ない一冊であると思います。特にNullについて書かれている13章はSQLに関わる全エンジニアにお勧めです。

プログラマのためのSQLでは

  • セルコの日記のようなページ
  • よくわからない雑学(グレゴリオ暦の話とか)
  • かなり複雑で長いSQLがほとんど説明がなく掲載されている(しかもエラーで動かないものも多い)
  • コンピュサーブ(昔のコミュニティサイト?)の投稿SQLなどが解説不足した状態で沢山掲載されている

などの一人で読むには辛い&冗長なページが沢山あるので個人で通読するにはあまり向かないような気がします。 という訳で様々なRDBベンダーのユーザーで集まってワイワイ話しながら読み進めることができたので、個人的に贅沢な読み方ができたのではないかと思います。

今から読むなら本書よりもミックさんが書かれている達人シリーズの方が本書のエッセンスが濃縮&抽出されていておすすめです。

達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ (CodeZine BOOKS)

達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ (CodeZine BOOKS)

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

今後目黒バイナリ勉強会では何を読むのか?

私が決めることではありませんが多分以下の2冊が候補になっていると思います。

  • 達人に学ぶSQL徹底指南書第2版
  • 失敗から学ぶRDBの正しい歩き方

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

失敗から学ぶRDBの正しい歩き方 (Software Design plus)

個人的に今後読む本

多方面から勧められていますが、今日ミックさんにも勧められたのでこの本は通読したい

論理学をつくる

論理学をつくる

参加者のみなさん、主催の木村さん、翻訳者のミックさん、そして原作者のセルコさんありがとうございました&お疲れ様でした。

分散システムにおけるイベントの前後関係と論理時計・ベクトル時計について

分散システムでは時刻同期はとらずに順序同期だけをとっている、という話はよく聞きます。順序同期とはいったいどのような仕組みなのでしょうか

前提

  • 各ノード間の処理の順序を適正に処理したい。
  • 個々のサーバーが原子時計などの十分に正確な時計を持っていることを前提としない
  • 時刻の責任を持つノード(FTP電波時計)のある仕組みは順序同期のためには使えない(ミリ秒単位の誤差が発生するため)

前後関係とは

プロセスローカルでの順序と送受信イベントからの推移測で導かれる順序のことを前後関係といいます。 前後関係には以下のルールがあります。

(ルールH1)同一プロセス上でおこなわれたイベントeとe'に対して、eがe'の後に行われていればe→e'が成り立つ
(ルールH2)eがメッセージmの送信イベント、e'がmの受信イベントであればe→e'が成立する
(ルールH3)e→e'、e'→e''ならば、e→e''

これらのルールから察する通り、すべてのイベント間の前後関係を定義できるわけではありません。 特に、本記事で紹介するアルゴリズムでは通信が行われていないノード間ではそれぞれのイベントがどちらが先に起こったか類推することはできません。

なおここで定義した前後関係は以下の特性があるため、半順序(Partial order)になります。

  • 満たしている:推移律(定義より自明)、反射率(自分自身と同値な関係)、反対象律(相違な2要素の同値な関係がない)
  • 満たしていない:完全律(比較できない相違な2要素がある)

比較できない関係を「平行である」といいます。例えば下記の例だと

f:id:yuyubu:20190327185600p:plain
a1とc1は比較できない

  • b1→b2(ルールH1)
  • a1→b1(ルールH2)
  • c1→b2(ルールH2)
  • a1→b2(ルールH3)

という前後関係を導出することができますが、 a1とc1に関しては前後関係の比較ができません。

論理時計

前後関係を実現する実装として「論理時計」というものがあります。 前後関係を定義したルール通通りそのまま実装すると、順序関係の保存と推移の計算などでパフォーマンス上あまり効率的ではありません。ローカルなイベント発生時のカウンタインクリメントと送受信イベント時のカウンターの比較・マージによって効率よくルールH1,H2,H3を満たす実装が論理時計です。

論理時計は以下のルールに沿って動作します

(ルールC1) 各プロセスは初期状態において、時計の時刻t=0である
(ルールC2) 受信以外のイベントeを行うときに、時計の時刻を1増やす
(ルールC3) メッセージ送信イベントeを行うときには、送信イベントeの発生時刻t(e)をメッセージに付加して送る
(ルールC4) メッセージ受信イベントeを行うときには、受信したメッセージに付加された時刻をt'とすると、t←max(t,t')+1として、この値をイベントeの発生時刻t(e)とする

論理時計を使うことで以下のことがわかります。

イベントeとe'でe→e'ならt(e)<t(e')

逆は成り立ちません。t(e)<t(e')ならe→e'ということは言えません。 たとえば下記の図だとt(a1)<t(c2)ですが、a1→c2ではありません。

f:id:yuyubu:20190327195010p:plain
t(a1)<t(c2)だがa1→c2ではない

ベクトル時計

論理時計より強力なベクトルクロックでt(e)<t(e')ならe→e'を成り立たせることができます。 プロセス数n,iэ{1,2,...n}の時ベクトル時計では各プロセスPiがベクトル時計Vi=(v1,v2,...vn)を持ちます。

ベクトル時計は以下のルールV1~V4で実現できます

(V1)各プロセスは初期状態において、ベクトル時計の時刻V=(0,0,...,0)である。
(V2) プロセスPiは受信以外のイベントeを行うときに、ベクトル時計の時刻V=(v1,v2,...,vn)にたいして、vi←vi + 1を行ったV'にベクトル時計の値をへんこうし、この値V'をイベントeの発生時刻V(e)とする
(V3) メッセージ送信イベントeを行うときには、送信イベントeの発生時刻V(e)をメッセージに付加して送る
(V4) プロセスPiがメッセージ受信イベントeを行うときには、受信したメッセージにふかされた時刻をV'=(v1',v2',...,vn')とすると、vj←max(vj,vj')(1<=j<=n)を実行し、さらにvi ← vi + 1を行った値にベクトル時計のアタを変更し、この時刻をイベントeの発生時刻V(e)とする

以下はこのベクトル時計Vを使う例です。ベクトル時計を青字で、論理時計を赤時で引き続き示しています。

f:id:yuyubu:20190327203433p:plain
a1→c2に順序関係がないことがV(a1)<V(a2)が成り立たないという結果からわかる

  • V(b2)=(1,2,2)とV(c2)=(0,0,2)ではV(c2)<V(b2)の大小関係があるのでc2→b2の前後関係があることがわかります。
  • V(a1)=(1,0,0)とV(c2)=(0,0,2)には大小関係がありませんので、a1とc2が並行であることがわかります。

この記事は「分散システム(情報工学レクチャーシリーズ)」の第2章 時刻と時計の個人的まとめです。

分散処理システム (情報工学レクチャーシリーズ)

分散処理システム (情報工学レクチャーシリーズ)

Linuxのパフォーマンス計測ツールの違いとか

詳解システムパフォーマンス4章で学んだことや、直近で利用したツール等のざっくりしたまとめ。引用はすべて詳解システムパフォーマンスの4章のもの。

f:id:yuyubu:20190304163451p:plain

カウンタタイプ

カーネルは、イベントの回数を数えたカウンタ( counter )と呼ばれるさまざまな統計を管理し ている。

カウンタはデフォルトで有効になっており、カーネルによって継続的にメンテナンスされている ので、 「タダ」で使えると考えてよい。カウンタを使うときの唯一の追加コストは、ユーザーラン ドからの値の読み出しである(無視できる)。

カーネルはIOやCPU利用の実績を/sys/proc配下のファイルに書き込んでいる。これは標準でデフォルトになっているため、読み出しコストのみで参照することができる。多くのXstat系のツールはこの形式になっている。

カウンタタイプのツールとしては

  • vmstat
  • iostat
  • sar

などがある。個人的にはdstatというstat族のキメラ的なツールがあるでそちらを利用している。

トレーサタイプ

イベントごとのデータを集めてくるツールのこと。システムコール、ネットワークパケット、ディスクIOなどのイベント発生時の情報を収集します。頻度の高いイベントを対象にすると観測自体に負荷が高いため、それを考慮に入れること。またパフォーマンスの都合上、デフォルトで無効にされているものもあるため、利用した後は無効にするなどしてもとに戻すこと。

トレーシングはデータをキャプチャするために CPU にオーバーヘッドをかけ、保存のためにかなりの量のストレージを必要とすることがあるため、一般にデフォルトでは有効にされていない。

プロファイラ

ある実行バイナリに異常な時間がかかっているというときはコンパイルデバッグオプションでビルドした後、gprofで処理に時間がかかっている関数を調べることができる。

  • gprof:C++で関数ごとのCPU利用率を調べる。
  • perf:機能が多いので紹介は略。pert top,perf reportなどでシステムコールなどの統計が取れる

参考:gprofの使い方 https://minus9d.hatenablog.com/entry/20140112/1389502918

他:timeコマンドのvオプション

timeコマンドだけでもioの回数やswappingの発生、rssなどがわかる。

参考:https://kuenishi.hatenadiary.jp/entry/2016/11/02/131046

上記の重厚な?ツール群を試す前に/usr/bin/time -vを使ってみるのもいいかもしれない。

詳解システムパフォーマンスおすすめです。

ubuntu18でCapsLockをESCキーとして使う

いらない子CapsLock*1をESCと入れかえました。

以下のサイトを参考

askubuntu.com

tweak toolを導入する

sudo apt install gnome-tweak-tool

tweaksを起動し、キーボードとマウス > キーボード > 追加のレイアウトオプション ボタンをクリック

f:id:yuyubu:20190208141236p:plain

Caps Lock behavior配下のラジオボタンで"Make Caps Lock an addtional Esc"を選択。再起動で有効になります。

結果:vimを使うときにホームポジションからESCが押せて便利。

*1:間違って押してパニックになる以外の使い方が無いような