GCE でemerge -e world, インスタンスはどれにする?

今後記事で詳しく書いていく予定なのだけれど, 最近Gentooの開発用のDockerイメージを作っている. Gentoo 公式のDockerイメージの gentoo/stage3-amd64 から ACCEPT_KEYWORDS=~amd64 emerge -uDN --with-bdeps=y world したものだと思えばよい.

https://hub.docker.com/r/naota/gentoo-devel/

毎日, そういうビルドを自分のマシンで回すというのもめんどうなので, GCE (Google Compute Engine)でやっていくことにした. こんな感じの ansible playbook でドーンとVMとsystemdのserviceがデプロイされる.

github.com

 

さて, ここでインスタンスをどうしするのがベストかな〜というのが問題となる. f1-micro や g1-small といった共有コアインスタンスから, 最大96コアのインスタンスまでいろいろありますね.

マシンタイプ  |  Compute Engine ドキュメント  |  Google Cloud Platform


とりあえず Gentooのビルドにどれがいいか調べてみよう. 標準マシンタイプでコア数を1, 2, 4, 8, 16まで変えてみる. (それに応じてメモリ量も増える) ワークロードとしては, emerge -e world (システムに入っている全パッケージのビルドし直し) をやっておく. Dockerのボリュームは, 永続SSD上に載っていて, ビルドもこのSSD上で行われる.

(なお, 16コアまでなのは, デフォルトで24コアまでのクォータがかかってるから. 96コアまで制限外してって言ってみたら先にマネーをある程度チャージしてくれとのこと, なるほど〜)


はい, ではemerge -eの時間はどうなったかなというのが下. 1コアだと4時間ぐらいかかっているのが, 16コアだと80分まで減ってる. すごい!!

f:id:meech:20180222211951p:plain


いや, でもちょっと待って. インスタンスはコア数によって価格が変わってしまいますね. 1コアのプリエンプティブ料金で1時間0.01ドル, 2コアで0.02ドルと, コア数 X $0.01 の料金がかかる (ゾーンはアイオワ, 多分一番安い). ということで, このワークロードを毎日1ケ月, 30日まわしたらおいくらかかる?というのが下

 

f:id:meech:20180222212023p:plain

いやぁ………コア数増やすほどコストは上がってますね…かなしい.

 

さて, ここでもう1つワークロードを見ておきましょう. 上記のように開発用のイメージを作るという場合であっても, 毎日同じパッケージをビルドする必要はありません. 一度ビルドしたものはバイナリパッケージとして保存しておいて, 新しい(アップデートされた)パッケージだけビルドしたらよくない? ということですね.

ということで, 次のワークロードは全部バイナリパッケージがあるという状態で, emerge -e world します. バイナリパッけージを展開して全部いれ直すというやつですね.

結果はこちら, ついでにコストも. コア数が1つで16分, 16個で10分とそれなりに減ってはいるけれどIOの方が多いワークロードだろうし, ビルドと比べるとそこまでは減っていってない. そんなもんだね

f:id:meech:20180222212049p:plain

f:id:meech:20180222212100p:plain


ということで, GCEでGentooの(最初の状態のシステムでの)ビルドをまわすとだいたいこの2つの線の間になる, ということでしょう. 月5ドルぐらいなら16コアでもいいかな〜とかコスパを考えて1コアかな〜とかそこはいろいろですかね.

 

f:id:meech:20180222212128p:plain

 

あとは上の方のインスタンスだとメモリが十分にあるので, /dev/shm でビルドしたりできるかな. そのへんはまた今度.

それと, これはstage3初期からのemerge -e worldだから, コア数によるスケール効果が出にくい小さなパッケージが多い, ということもある. もしこれがKDE全部ビルドするなどなると, きっといろいろ変わってくるでしょうね.

(それにしても, 112パッケージ(圧縮した状態で, 223M)をバイナリでインストールで10分ってちょっと遅いような気もする. 他だとどんなもんなんだろ. Archとかはやそうだが)

GentooのBINPKGの複数インスタンスとか

FEATURES=binpkg-multi-instance の話をもっとと見かけたのでもっと

これまで Gentooのバイナリパッケージを生成すると, /usr/portage/packages/app-shells/bash-4.4_p18.tbz2 のような感じで, /usr/portage/packages の下に "<category>/<package>-<version>.tbz2" というファイル名で保存されてきた. この保存ファイル名は, Gentooにしては異常に窮屈なものだ.

Gentooのパッケージというのは, USEフラグをいろいろ変えたり, ビルド時に依存関係が書きこまれたりする. バイナリパッケージがあっても, もし, これらUSEフラグなどが異なればそのバイナリパッケージを使うことができない. もともとのファイル名だと, 「最後にビルドした時」と同じUSEフラグなどでなければ, そのパッケージは使えない. Gentoo でそんなことできるはずがない. いろんなマシン用にバイナリをホストしだすとますます大きな問題になってしまう. だからGentooでバイナリパッケージがはやらないんだ(ちがう?

さて, では FEATURES=binpkg-multi-instance にするとどうなるの? バイナリパッケージのファイル名が "app-shells/bash/bash-4.4_p18-1.xpak" のように "<category>/<package>/<package>-<version>-<buildID>.xpak" となる. <version>の後に<BuildID>がついた. このIDにより, 同じパッケージ・バージョンでも別のバイナリパッケージを置けるようになるのだ. やったぜ

BuildID はどうやって決まるのか? これはパッケージごとに最初は"1", その次は"2"みたいに単調にインクリメントされる. (なんかびみょうな実装なような気がするぞ, いいけど) そして, いまのところquickpkgは同じconfigかどうかなどチェックしていないようだ. すなわち, FEATURES=binpkg-multi-instance して, 同じパッケージを quickpkg するとどんどん BuildID が増えていく.


BINPKGの複数インスタンスは, こんな話でした


ちなみにだけれど, この新しいファイル配置は昔のファイル配置と互換性があるみたい. なんで, さくっと FEATURES=binpkg-multi-instance しちゃっていいと思う. いろんなマシン用にバイナリパッケージ作っている人には, ほんとにべんりになると思う.

GentooのBINPKGの圧縮とか複数インスタンスとか

ひさしぶりにman make.confなど見ていると, `BINPKG_COMPRESS` という変数があったのに気がついた. Gentooのバイナリパッケージの圧縮方法を選べるやつで, "bzip2, gzip, lz4, lzip, lzop, xz, zstd"に対応している.

話題のzstdもあるじゃん. どんぐらい圧縮時間やらサイズやら変わるんだろうね, ということでやってみた

 

結果とスクリプトはこちら

gist88666f8b36ae927edefef14486572565

とりあえずグラフにしてみよう.

f:id:meech:20180221125656p:plain

f:id:meech:20180221125708p:plain

(しれっとlzopないけど、package入れてなかったのでとばした)

サイズでいうとlzipかxz, まあxzの方がよく見るしxzを選ぶといい感じだろうか. ただパフォーマンスは悪い

パフォーマンスでいうとlz4とzstdがよい. ただサイズも見ると, zstd一択といったところか.

よく使われるbzip2とgzipは, よく知られている通りのぼちぼちな結果で, 互いにサイズとパフォーマンスにトレードオフがある. そうだね.


サイズで選んでxzなのか, パフォーマンスで選んでzstdなのか, (デフォルトのbzip2のぼちぼちでいくか)Gentooのバイナリパッケージにはどちらがよいだろうか.

xzの場合, 全体で300秒ほどかかっているが, 今回作ったパッケージは45個なので1つあたりの平均6.76秒.

一方, xzはzstdに対して72%のサイズになる.

5,6秒ならコンパイル時間に比べれば些細なものだし, サイズをとってxzでいいのではないか. 展開は速くていいみたいだしね, debianもそうしてるよね

 

 

 

 

投稿して気づいたけど、 複数インスタンスの話をしてねえ!!

FEATURES=binpkg-multi-instance ってのが増えて, USEフラグ違いとかdepend違いで同じebuildに対して個別にbinary packageを作れるようになった。べんり〜。これでいろんなUSEとかでもbinary packageを活用できるようになったぞ!

Gentooのbinary packageの夜明けじゃああ!

今日のLKML: テストしてテストしてからbitを立てる

こういうpatchが投稿されている

[10/12] writeback: only allow one inflight and pending full flush - Patchwork

full flushが一度に1つまでしか走らないようにしたいね、というものなのだけど、中身は本題には関係はない

patchの中に以下のようなコードがある

+       if (test_bit(WB_start_all, &wb->state))
+               return;
+
+       set_bit(WB_start_all, &wb->state); 

 "wb->state"のWB_start_allのbitが立っていたらreturnして、立ってなかったら立てる、というもの。このWB_start_allで"full flush"の排他をしていることになる。

厳密には、これは排他になっていない。1つ目のスレッドがtest_bit()してから、set_bit()するまでの間に、2つ目のスレッドがtest_bit()でbitを見にいくことがある。その場合、2つのスレッドが同時に"critical section"に入ってしまう。

ここを厳密な排他にするには、test_and_set_bit()を使う。

linux/atomic.h at master · torvalds/linux · GitHub

もちろんそんなことはkernel developerには、わかっている。patchのコメントは以下のように書いてある

It doesn't matter if we race a little bit on this, so use the faster separate test/set bit variants. 

 もともとfull syncが2つ以上一緒に走ると重いよね、ぐらいのものなのでここで厳密な排他は必要ないのだ。

こういうatomicityが必要でない場合には、test_bit()とset_bit()とを分けた方がいい時もある。

test_and_set_bit()を使うと必ずbitを立てる、すなわち値を更新してしまう。すると、cache lineがdirtyになる。頻繁に呼ばれ、多くの場合にbitが立っているような場所では、この「cache lineを汚すかどうか」が大きな違いになってくる。

 

で、頻繁に呼ばれるところで無駄にcache lineを汚さずにtest_and_set_bitしたいな〜となると以下のようなコードを書くことになる

if (test_bit(bit, addr) || test_and_set_bit(bit, addr))
...

 スレッドはpatchの内容をすっかり離れて、こうした"idiom"のdocumentないし、それもあわせてなんか関数作っとく?という話になる。そこで関数の名前の話になり、"test_and_test_and_set_bit"がいいとか、"test_then_test_and_set_bit"がいいとか、patchの話どうしたみたいな感じになっていってる。

まあ以下のように解説があるぐらい知られている話なのだけど、関数名として見ると"test and test and set bit"ってなんかむずっとする名前だよな・・・と思った次第

Test and test-and-set - Wikipedia

systemd-networkdがオンラインにならないの

sysmtedはなんでもできるつおい子なので、ネットワークの設定もやってくれる. つおい

どうやって設定書くの? ということで、 man systemd.network すると, こんな風に使える感じのサンプルが載っている

Example 2. DHCP on ethernet links

# /etc/systemd/network/80-dhcp.network
[Match]
Name=en*

[Network]
DHCP=yes

This will enable DHCPv4 and DHCPv6 on all interfaces with names starting with "en" (i.e. ethernet interfaces).

だいたいDHCPでよろしくやるとべんりなので、これをこぴぺするわけだが、これで困ったことになったという話。ちゃんと名前を明示しないと、オンラインになるまで待つserviceがタイムアウトまで待ってしまう

NFSと自動生成サービス

systemdは/etc/fstabからそれぞれのmount pointに対応したserviceを自動的に生成する。 mountもserviceの起動という形で行われる。たとえば、以下のようなfstabを書いておく

naota:/linux /remote/linux nfs defaults,hard,intr 0 0

 すると、以下のようにservice fileが自動生成される

$ sudo systemctl cat remote-linux.mount
# /run/systemd/generator/remote-linux.mount
# Automatically generated by systemd-fstab-generator

[Unit]
SourcePath=/etc/fstab
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
Before=remote-fs.target

[Mount]
Where=/remote/linux
What=naota:/linux
Type=nfs
Options=defaults,hard,intr

 この時mount先がNFSなので、ここには明示的には書いていないが、"network-online.target"への依存が自動的に付加される。すなわち, networkがオンラインになってからNFSにつなぎにいく, ということになる。

systemd-networkdでのnetwork-online.target

systemd-networdを使う場合、network-online.targetの前に、systemd-networkd-wait-online.serviceが起動される。

$ sudo systemctl cat systemd-networkd-wait-online.service
# /lib/systemd/system/systemd-networkd-wait-online.service
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

[Unit]
Description=Wait for Network to be Configured
Documentation=man:systemd-networkd-wait-online.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Requires=systemd-networkd.service
After=systemd-networkd.service
Before=network-online.target

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-networkd-wait-online
RemainAfterExit=yes

[Install]
WantedBy=network-online.target

 このserviceは、"/lib/systemd/systemd-networkd-wait-online"を起動する、というもの。 このプログラム(systemd-networkd-wait-online)は、マシンのnetwork interfaceをなめて全て(loなどはのぞくが)つながるまで待つ。

具体的にどうやって「つながった」と判定されるのか見ていこう。/run/systemd/netif/links/*にinterfaceの数だけのファイルがある。たとえばloだとこんな感じ。

$ cat /run/systemd/netif/links/1
# This is private data. Do not parse.
ADMIN_STATE=unmanaged
OPER_STATE=carrier

特に管理対象じゃありません(ADMIN_STATE=unmaged)となっている。

一方でDHCPにつないでるやつだとこんな感じ。 DHCPIPアドレスをもらっていて、 ADMIN_STATE=configuredとなっている。

$ cat /run/systemd/netif/links/3
# This is private data. Do not parse.
ADMIN_STATE=configured
OPER_STATE=routable
NETWORK_FILE=/etc/systemd/network/80-dhcp.network
DNS=なんたらかんたら
NTP=なんとかかんとか
DOMAINS=
ROUTE_DOMAINS=
LLMNR=yes
MDNS=no
ADDRESSES=.....
ROUTES=.....
DHCP4_ADDRESS=.....
DHCP_LEASE=/run/systemd/netif/leases/3

 DHCP待ちなどのタイミングでは、これが"ADMIN_STATE=configuring"となっている。

結局のところ、systemd-networkd-wait-onlineはこのへんのファイルをなめて、ADMIN_STATE=configuring(やpending)の間ループするといった動きをする。

もしも、configuredにならない場合にはdefaultで120秒のタイムアウトで終了する

LANケーブルをさしてない口があるとつらい

といった状況でLANケーブルをさしていないportがあると、その口は"configuring"のままなのでsystemd-networkd-wait-onlineはタイムアウトするまで待ちつづける. 結果として、あちこち起動が遅れてめんどいなあという気持ちになる

それでどうするの?

Name=en*にしとくとdevice名見なくてもあれこれできるけど、オンライン待ちがうざいのでちゃんと名前を書こうねという話だった

 

変愚蛮怒をXft対応する話 Part1 X11における変愚蛮怒の描画コード

変愚蛮怒というRoguelikeゲームはLinuxでも動作するけれど, TrueTypeフォントが扱えなくてフォント指定がかなりだるいし, レンダリングがびみょう. 変愚蛮怒の描画をXftで行うようにした. フォントがきれいになって, お好きなフォントを使えて超ハッピー. この経験をふまえて, libX11のものをXft対応ってどうやったらいいのかを書く.

リポジトリはここ

github.com

変愚蛮怒ってなに

変愚蛮怒はMoria/Angbandから始まる*band系ローグライクゲームのバリアント(変種)の一種です。直接にはZangbandから派生しています。
鉄獄100Fに潜むラストボス『混沌のサーペント』を撃破して『*勝利*』を遂げるためには、キャラクターのレベルや装備だけでなく、*あなた*自身の習熟が求められます。

変愚蛮怒 公式WEB

まあ日本で派生したこんな感じなローグライクゲームで, 日本で派生しているので斬鉄剣持ったサムライっぽい人とか, 光の剣をはなつ不老不死にして史上最強のエスパーとか, 石仮面を持った吸血鬼とか出てくる. 主人公の方も剣術家になって天翔龍閃したり, 鏡使いになってラフノールの鏡したりできる.

https://pbs.twimg.com/media/DBjx6C6VoAA231H.jpg:large

最近だと東方成分の入ったバリアントもあるみたい. 当方東方ネタはあんまわからんのだけど, わかる人はよさそう.

www65.atwiki.jp

Linuxにおける変愚蛮怒

変愚蛮怒Linuxでも動いてとてもサイコー. ただコードが昔に書かれたものであるため, 現状とあわない点が様々ある

  • 内部コードがEUC (これは簡単に文字幅とか計算するのにはしかたなかったり)
    • 表示もこの前までEUCでしようとしていたがpatchが当たった: チケット #25917: UTF-8サポート - 変愚蛮怒 - OSDN
      • これもまだ, UTF-8読んでEUCにして, またUTF-8に戻して表示しててなんかアレなんだけどまあ既存ルーチンいじらないといけないしな〜
      • あと, UTF-8のCファイルをgcc-wrapというスクリプトで, EUCにconvertしてからcompileしていてまたむずかしい. atomicではないからCtrl-cするとめっちゃdiffが出たりする
  • TrueTypeフォントがうまくあつかえない
    • フォント指定には昔ながらの, XLFD (-*-*-medium-r-normal--24-*-*-*-*-*-iso8859-1ってやつ)を使う
      • めちゃ複雑わからん
    • mkfontdir, mkfontscaleなど使うとTTFも使えるらしい?んだけどうまく動かんわ
      • 昔動いてたんだけど…もうきえた?
    • bitmapフォントは…見た目やっぱアレ

現在のX11用描画コード

ということで, 変愚蛮怒をXftというTrueTypeフォントを扱えるライブラリで描画するように書き変えよう, という話.

今回は現在の描画コードについて整理する.

変愚蛮怒WindowsやらX11やらCursesやら様々な環境に対応できるように, 描画対象をTermと抽象化している. 各環境では以下のhookを実装して, 描画を行う.

いろいろhookはあるけれど, 主に仕事をするのはtext_hookになる. X11での実装は以下の通り:

(x, y)の位置に, 属性番号aで, nバイト長の文字列sを描画します, ということですね. Infoclr_set(clr[a]);で描画色を設定して, Infofnt_text_std(x, y, s, n);でテキスト描画している感じ. ここの(x, y)は文字単位での座標. ピクセル単位での座標へはInfofnt_text_std()内で変換する.

Infofnt_text_std()は以下のあたりから. ifdefとかが多いので抜粋する.

https://github.com/naota/hengband/blob/master/src/main-x11.c#L1605

static errr Infofnt_text_std(int x, int y, cptr str, int len)
{
    /*** Decide where to place the string, vertically ***/

    /* Ignore Vertical Justifications */
    y = (y * Infofnt->hgt) + Infofnt->asc + Infowin->oy;

    /*** Decide where to place the string, horizontally ***/

    /* Line up with x at left edge of column 'x' */
    x = (x * Infofnt->wid) + Infowin->ox;

    /*** Actually draw 'str' onto the infowin ***/
...
    /*** Handle the fake mono we can enforce on fonts ***/

    /* Monotize the font */
    if (Infofnt->mono)
...
    /* Assume monoospaced font */
    else
    {
...
        XmbDrawImageString(Metadpy->dpy, Infowin->win, Infofnt->info, Infoclr->gc, x, y, kanji, kp-kanji);
        free(kanji);
    }

    /* Success */
    return (0);
}

まず, 引数の文字単位の座標である(x, y)ピクセル単位に変換して, 描画位置を決める. フォントの高さ, 幅はどのフォントでも一定(monospaceフォント)と仮定しているので, 基本的にはx, yにそれぞれフォントの幅とフォントの高さを掛ければよい. これに描画の開始座標の(Infowin->ox, Infowin->oy)を足す. また, Y座標にはフォントのアセント(フォントのベースラインから上端までの高さ)Infofnt->ascを足す. これで, (x * フォントの幅 + ox, y * フォントの高さ + oy)ピクセルに文字の左上が当たるように描画されることになる.

あとはkanjiUTF-8が入っているので, そいつをXmbDrawImageString()で描画する. XサーバがMetadpy->dpy(XのDISPLAYのやつ)で, 描画先がInfowin->winになる. フォントがInfofnt->infoで指定され, 色がInfoclr->gcで指定される.

Xft化することを考えると, Infowin, Infofnt, Infoclrの初期化とかを見ておくとべんりそう.

(しかし, #define DPY (Metadpy->dpy)とかしてるのに全然使ってなくてあんまりコードの治安はよくない)

ウィンドウの管理: Infowin

struct infowin Infowinは以下のような変数. ウィンドウの管理をする.

struct infowin
{
    Window win;
#ifdef USE_XIM
    XIC xic;
    long xic_mask;
#endif
...
};

InfowinのデータはInfowin_prepare()で初期化されている.

Xft化する時に, ウィンドウ関係でなにか追加でほしい時は, 上の構造体に変数増やして, Infowin_prepare()で初期化したらいいかな, という感じ.

フォントの管理: Infofnt

struct infofnt Infofntは以下のような変数. フォントの管理をする.

struct infofnt
{
    XFontSet info;

    cptr name;

    s16b wid;
    s16b twid;
    s16b hgt;
    s16b asc;
...
};

ここに, フォント, その幅・高さ・アセントなどが保持されている. 初期化はInfofnt_prepare()で行う.

static errr Infofnt_prepare(XFontSet info)
{
    infofnt *ifnt = Infofnt;

    XCharStruct *cs;
    XFontStruct **fontinfo;
    char **fontname;
    int n_fonts;
    int ascent, descent, width;

    /* Assign the struct */
    ifnt->info = info;

    n_fonts = XFontsOfFontSet(info, &fontinfo, &fontname);

    ascent = descent = width = 0;
    while(n_fonts-- > 0){
        cs = &((*fontinfo)->max_bounds);
        if(ascent < (*fontinfo)->ascent) ascent = (*fontinfo)->ascent;
        if(descent < (*fontinfo)->descent) descent = (*fontinfo)->descent;
        if(((*fontinfo)->max_byte1) > 0){
            /* 多バイト文字の場合は幅半分(端数切り上げ)で評価する */
            if(width < (cs->width+1)/2) width = (cs->width+1)/2;
        }else{
            if(width < cs->width) width = cs->width;
        }
        fontinfo++;
        fontname++;
    }
    ifnt->asc = ascent;
    ifnt->hgt = ascent + descent;
    ifnt->wid = width;
...
    /* Success */
    return (0);
}

XFontsOfFontSet()でフォント一覧をとってきて, そのリストをなめて, 最大のアセント・デセントを求める. 幅は多バイト文字なら, フォント幅の半分と計算していて, 多バイト文字の幅が単一バイト文字の幅の2倍と仮定しているとわかる.

Xftでも, このへんでアセントとかを計算したらいいね, ということになる.

色の管理: Infoclr

Infoclrは以下の通り. GCがXlibのグラフィックコンテキストで, 描画色・背景色などを保持してくれる. ついでに, fg, bgでも描画色・背景色を保持しているけど, べつに使っていない.

struct infoclr
{
    GC gc;

    Pixell fg;
    Pixell bg
...
};

これまでの予想に反して, Infoclrのデータの初期化はinit_x11()の中で行われている.

errr init_x11(int argc, char *argv[])
{
...
    /* Prepare normal colors */
    for (i = 0; i < 256; ++i)
    {
        Pixell pixel;

        MAKE(clr[i], infoclr);

        Infoclr_set(clr[i]);

        /* Acquire Angband colors */
        color_table[i][0] = angband_color_table[i][0];
        color_table[i][1] = angband_color_table[i][1];
        color_table[i][2] = angband_color_table[i][2];
        color_table[i][3] = angband_color_table[i][3];

        /* Default to monochrome */
        pixel = ((i == 0) ? Metadpy->bg : Metadpy->fg);

        /* Handle color */
        if (Metadpy->color)
        {
            /* Create pixel */
            pixel = create_pixel(Metadpy->dpy,
                         color_table[i][1],
                         color_table[i][2],
                         color_table[i][3]);
        }

        /* Initialize the color */
        Infoclr_init_ppn(pixel, Metadpy->bg, "cpy", 0);
    }

angband_color_table[]にARGBが入っている. create_pixelPixell(X11の場合, XColorのtypedef)を作り, その色を描画色としてGCを設定する. Infoclr_set(clr[i])Infoclr = clr[i]となって, Infoclr_init_ppn()で色が設定される. 結局,のところ配列clr[]に各色でGCが格納される.

(ここで256個のGC作っているけど, 実際には8色しかない. alpha == 0xFFあたりでbreakしちゃってもいい気もする)

まとめ

Infowin, Infofnt, Infoclrがそれぞれ現在のウィンドウ・フォント・描画色を管理する.

InfowinInfowin_prepare()で, InfofntInfofnt_prepare()で, Infoclr(clr[])はinit_x11()で初期化される.

Term_text_x11()が指定位置にテキストを描画する. Infoclr_set(clr[a]);で色を変えて, Infofnt_text_std(x, y, s, n);でテキスト描画する. 実際に描画しているのは, XmbDrawImageString().

次回はXmbDrawImageString()をXftのテキスト描画関数に置き換える. それにあたって, 描画先・フォント・色の構造体が変わるので必要な処理を初期化関数に追加するなどについて書く.

Linux kernelをninjaでビルドする

ninjaってなに

Ninja, a small build system with a focus on speed

make代替みたいなビルドシステムで, 小さくてスピードが速いのが特徴となっている. Chromiumとかで使われている様子.

Makefileを読めるわけではなく, ninja用のビルドファイルが必要.

kninja

GitHub - rabinv/kninja: Ninja build file generator for the Linux kernel

kninjaはLinux kernel用にkninjaのビルドファイルを生成してくれるスクリプト.

はやいの?

kninjaによるルール生成にはkninja.pyを使う. ルール生成のためにmakeしてから, “make -p"の情報からビルドファイルを生成する. したがって, 下のようにルール生成まで入れると1分ほどkninjaの方が遅くなる.

フルビルドの場合

$ make clean
$ time make -j$(nproc)
real    4m21.090s
user    41m19.146s
sys     2m48.689s
$ make clean
$ time python3.4 ~naota/src/kninja/kninja.py
[INFO] Ensuring full build: make -j 12
…
[INFO] Generating make database: make -p
[INFO] Caching make database to .makedb
[INFO] Parsing make database (2563191 lines)
[INFO] Wrote build.ninja (3282 rules, 3255 build statements)
[INFO] Wrote .ninja_deps (2597 targets, 1144879 deps)
[INFO] Wrote .ninja_log (3282 commands)
[INFO] Checking ninja status: ninja -d explain -n
[INFO] All OK

real    5m15.829s
user    40m58.248s
sys     2m59.674s

ビルド済

$ time make -j$(nproc)
real    0m5.883s
user    0m24.015s
sys     0m9.905s
$ time python3.4 ~naota/src/kninja/kninja.py
real    1m4.807s
user    1m12.236s
sys     0m23.291s

ただ, これはルール生成をしているから遅いのであって, 一度ルールが作られてしまえばninjaの方が速い. たとえば, なんの変更もない時は以下のようにmakeだと6秒に対して, ninjaは1秒かからない. (ただこれはCHKとかCALLとかでビルドで関係ないチェックスクリプトが走っているのもあるか)

$ time make -j$(nproc)
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
  CHK     include/generated/timeconst.h
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CHK     kernel/config_data.h
…
real    0m6.005s
user    0m24.038s
sys     0m9.827s
$ time ninja
ninja: no work to do.

real    0m0.204s
user    0m0.152s
sys     0m0.052s

1つのファイルを変更した場合は, makeで7秒, ninjaで3秒といったところ.

$ touch fs/btrfs/ctree.c; time make -j$(nproc)
…
real    0m7.129s
user    0m27.334s
sys     0m10.295s
$ touch fs/btrfs/ctree.c; time ninja
[3/3] ld -r -m elf_x86_64 -T ./scripts/module-common.lds --build-id -o fs/btrfs/btrfs.ko fs/btrfs/btrfs.o fs/btrfs/btrfs.mod.o ; true

real    0m2.922s
user    0m2.755s
sys     0m0.222s

速くはなるので, inotifywaitでいじっているファイルを監視して, ninjaを走らせるループなどしておくとべんり. (while inotifywait fs/btrfs/*.c; do ninja ;done とか)

ただ.configやKconfigやらがupdateされるたびにルールを作り直す必要があるため, 全くLinux kernelを開発しませーん, ビルドしてインストールするだけです〜という人にはいらないツールになる.

compile DBを生成する

Emacsだとirony-mode, Atomだとlinter-clangやautocomplete-clangのように, clangを使ってCのsyntax checkや補完をしてくれるパッケージがある.

これらを動かすにあたって, include pathやらdefineなどなどコンパイルオプションを適切に渡す必要がある. このへんのツールは"compile_commands.json"に書いてある, コンパイルオプションを読んでくれる.

ninjaには, このjsonファイルをdumpする機能がある. ninja -t compdbで以降の引数で指定されたルールのjsonをはきだすので下のようなコマンドを実行すると全ルールとれる.

$ ninja -t compdb $(awk '/^rule /{print $2}' build.ninja) > compile_commands.json

ironyはなんやらうまくやってくれてる感じあるけど, gccだけが対応していてclangが対応していないoptionがあるとAtomの子たちはだいたいうまく動かない. よって, こんな感じで適当にargを削ってあげるといい.

DELETE_ARGS=(
    '-falign-jumps=1'
    '-falign-loops=1'
    '-fconserve-stack'
    '-fno-delete-null-pointer-checks'
    '-fno-var-tracking-assignments'
    '-mfentry'
    '-mno-fp-ret-in-387'
    '-mpreferred-stack-boundary=3'
    '-mskip-rax-setup'
    '--param=allow-store-data-races=0'
    '-DCC_HAVE_ASM_GOTO'
    '-Wno-frame-address'
    '-Wno-unused-but-set-variable'
    '-Werror=designated-init'
)
script=""
for x in ${DELETE_ARGS[@]};do
    script="s/$x//;${script}"
done
sed -i -e "${script}" compile_commands.json

compdbを使えばheader情報とかも追加できる

GitHub - Sarcasm/compdb: The compilation database Swiss army knife

いろいろ入れてこんな感じか

gist.github.com

所感

なんだかんだEmacsのパッケージの方がCみたいな言語にはつよいね