読者です 読者をやめる 読者になる 読者になる

Linuxデスクトップ環境2017

流行っていたので便乗

ディストリビューション

Void Linux です。昨年の頭まではArch Linuxを使っていました。

Void LinuxNetBSDの元メンテナーが作ったディストリビューションです。
独自のパッケージシステムxbpsや、systemdではなくrunitというdaemontoolsに近いサービス管理が特徴となっています。
BSD文化を感じることのできるLinuxとなっており、シンプルでエレガント、保守性が高いと言えると思います。

パッケージシステムは出来がよく、別のアーキテクチャ用のパッケージビルドがpacmanに比べて楽です。
まぁ、とはいえ最新への追随が早かったりと、Arch Linuxとは似ています。是非触ってみてください。

デスクトップ環境

GNOME3です。これは3.0のリリース直後、ここ5年ぐらいずっと使っています。

実は、GNOME3はjournald, logindというsystemdのコンポーネントに依存しています。
そのため、Void Linuxではこれらをスタンドアロンな代替を使った上でxbpsに収録しています。
これが災いし、GNOMEのバージョン追随は遅れてしまっています。
もはやGNOME3も相当にstableなのであんあり気にはなりませんが。

日本語入力

ibus-skkを使っています。
一時期の状況に比べると、相当改善されましたね。

私はqwertyではなくdvorak配列を常用していますが、現在のGNOME3では最後に選択したxkb配列がibusの配列になるという仕様(?)で、これが便利です。

ターミナルエミュレータ

st

X Window Systemのための、最少主義なターミナルです。
rxvtのように、もはや使われない端末のエミュレートを省き、コードベースが5000行弱の1ファイルになっています。
コンパイル時に設定をヘッダファイルで書くことで、コンパイル後は小さな可搬性の高いバイナリができあがります。かっこいい。

ただ、最近Rustで書かれたAlacrittyGPU支援による描画を全面に押し出していて、waylandとの相性に優れていそうなので、移住を考えています。

カラースキーム

paletteというスキームを自作しました。

16色をyamlで定義し、https://github.com/chriskempson/base16というスタックを使うことで、各種アプリケーション向けの設定ファイルを書きだすことができます。
(少し前にビルドシステムに手が入ったため、さっきリポジトリを作った)

エディタ

neovim + vim-plug を、ターミナルエミュレータ機能、タブ機能、分割機能を重用しつつ使っています。
もともとはbyobuを使っていたんですが、neovimで不要になりました。すごいですね。

ウェブブラウザ

FirefoxvimFxを導入しています。

vimFxはvimperatorライクな拡張機能です。
vimperatorに比べると細かに触ることなく、ある程度使いやすいデフォルト設定を持っていると思います。

フォント

M+フォント

システムワイドでM+ 1c、ターミナルではM+ 1mnをfontconfigで設定しています。

それでは皆様。2017年も良いLinuxデスクトップライフを。

Rustのコンパイラプラグインで実装を型定義から自動生成しよう

しよう(提案)

この記事はRust Advent Calendar 2016 - Qiitaの9日目の記事となります。

コンパイラプラグイン

Rustはコンパイラであるrustcに対して、ユーザーがプラグインとして機能を追加できる仕組みがあります。 コンパイル時に処理を定義できる仕組みというと、マクロが思い浮かびますが、

macro_rules! foo {
    (x => $e:expr) => (println!("mode X: {}", $e));
    (y => $e:expr) => (println!("mode Y: {}", $e));
}

fn main() {
    foo!(y => 3);
}

実行結果

=> mode Y: 3

と上記リンクのドキュメントにあるように、マクロが限られた範囲のASTをパターンマッチして変更するのに対し、コンパイラプラグインでは、こんな感じで、Rustそのものを使ってソースコードのASTを処理し、新たなASTを構築できます。

例えば、Rustでよく使われるシリアライズライブラリであるserdeがありますが、serdeを構成するcrateのうちの1つ、serde_deriveは

#[derive(Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

のようにSerialize, Deserializeという属性を指定することで、それぞれser::Serialize, de:Deserializeトレイトに必要なメソッドのASTを、構造体の名前と型から生成する機能を持っています。

今回は、このアトリビュートを独自に定義し、コンパイラプラグインとして使うことのできるcrateを作成しました。

題材

mysqlライブラリである

https://github.com/blackbeam/rust-mysql-simple

のトレイト、FromRowを、構造体に対して実装しようと思います。

#[derive(FromMysqlRow)]
struct User {
    name: String,
    age: u64
}

のようにすると、SELECT文を発行した結果が格納されているmysql::Rowから、このUser構造体への変換メソッドが自動的に生成されます。

実装

github.com

実装は https://github.com/rail44/rust-mysql-derive/blob/master/src/lib.rs だけです。

この題材で実装を始めてから気付いたのですが、現在のrust-nightlyではRFC#1681により、上の方でリンクを貼ったコンパイラプラグインのドキュメントとはプラグイン登録のためのAPIが変わってしまっていました。 そのため、実装の上では既にnightly対応がなされているserdeの

https://github.com/serde-rs/serde/tree/master/serde_derive

https://github.com/serde-rs/serde/tree/master/serde_codegen

https://github.com/serde-rs/serde/tree/master/serde_codegen_internals

3つのcrateを参考にしました。

新しいマクロシステム(macros 1.1と呼ぶらしい)では、lib.rsで

#![feature(proc_macro, proc_macro_lib)]

extern crate proc_macro;

と宣言をした上で

https://github.com/rail44/rust-mysql-derive/blob/master/src/lib.rs#L12

#[proc_macro_derive(FromMysqlRow)]
pub fn derive_from_mysql_row(input: TokenStream) -> TokenStream {
    let source = input.to_string();
    let ast = syn::parse_macro_input(&source).unwrap();
    let expanded = expand_from_mysql_row(&ast);
    expanded.parse().unwrap()
}

のように #[proc_macro_derive(...)]アトリビュートを付けた関数を定義することで、独自のアトリビュートを作成し、外部からも使うことができるようになります。

serdeではTokenStreamをパースした上でASTの追加を行っています。 そのパースのためのcrateが

github.com

として公開されていたため、ありがたく使わせて頂きました。 expand_from_mysql_row関数がASTを生成する本体の実装になっていますが、大体syn crateのドキュメント通りです。ありがたや。

結果

テストコードでは

https://github.com/rail44/rust-mysql-derive/blob/master/tests/test.rs

#[derive(FromMysqlRow, Debug, PartialEq, Eq)]
struct User {
    name: String,
    age: u64
}

と書くだけで、

    let users: Vec<User> = pool
        .prep_exec("SELECT * FROM rust_mysql_derive.users", ())
        .unwrap()
        .map(|opt_row| mysql::from_row(opt_row.unwrap()))
.collect();

のように、クエリの結果を構造体に変換できるようになっています。

mysql crateのドキュメントにあるような方法 mysql::Row - Rust

pool.prep_exec("SELECT * FROM tmp.Users", ()).map(|mut result| {
    let mut row = result.next().unwrap().unwrap();
    let id: u32 = row.take("id").unwrap();
    let name: String = row.take("name").unwrap();
    let age: u32 = row.take("age").unwrap();
    let email: String = row.take("email").unwrap();

    assert_eq!(1, id);
    assert_eq!("John", name);
    assert_eq!(17, age);
    assert_eq!("foo@bar.baz", email);
});

に比べて簡単に見えます。見えますよね?たぶん見える。

$ docker run -ti --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw mariadb

などとすれば cargo test でテストが走ります。

この程度の実装なら型毎に手で書けばよくない?

僕もそう思います

Rustでインタープリターを書いた話

嘘です。

Rustでインタープリターを書いている話

こんにちは。VOYAGE GROUPの子会社、fluctで学生アルバイトをしているアメミヤです。
日程調整のスプレッドシートにしれっと記入したら、何も怒られなかったので、本記事はVOYAGE GROUP Advent Calendar 2015の11日目となります。

RustはMozillaが開発している、マルチパラダイムの静的型付け言語で、パターンマッチや強力な型推論ジェネリクスやそれを柔軟にする型クラスに近いトレイトを持っています。
中でも特徴的なのは、変数の所有権やポインターの貸し出しといった概念で、これによりGCを用いずに静的にメモリ領域の寿命を計算することができます。

詳しくは公式サイト今年のRust Adevent Calenderへ。
最近は落ち着いてきたものの、依然として変更の激しい言語なので、参考にする時はなるべく新しい文書の方がベターです。

今回はLLVM IRへのコンパイラを書き始めたのですが、思っていた以上にLLVM分からないマンだったのでEvaluaterの方を実装してインタープリタになってしまいました。

github.com

ビルド・実行

cargo run -- [file]

Cargoについて詳しくは今年のRust Ad以下略
後述しますが、rustcコンパイラへのプラグインを使っているため、この機能の存在するRust Nightlyチャンネルでの実装になっています。
Stableチャンネルではビルドが失敗します。

文法

オレオレ。こんなのを予定(妄想)しています。

fizzbuzz: {
  is_fizz: i % 3 == 0
  is_buzz: i % 5 == 0
  fizz: if is_fizz "fizz" ""
  buzz: if is_buzz "buzz" ""

  fizz.concat{buzz}
}

range{min: 1 max: 100}.map{i: fizzbuzz}

いまはこんな程度しか動きません/(^o^)\

i: j / 2
j: 5
k: k + 1

i * (j + 3) - (j / i)

遅延評価戦略(言いたいだけ)を実装しており、後に出てくるjをiの定義で使うことができます。
また、無限に再帰してしまうkは必要がないため評価されません。

なぜ作っているのか?

現在、社内では読書会/勉強会という形でSICPTAPLアンダースタンディングコンピューテーションといった書籍を読み進めているのですが、そのどれもに、言語処理系を実装する章が存在しています。
そこで、実際に手を付ける前に自分で試行錯誤してみたくなってみた。というのが今回の動機になっています。

言語作ること自体初めてですし、お勉強もそっち方面ではないです。なので、コードは地獄です。破茶滅茶です。ゆるして。

使ったCrates

crateはRubyでいうgem、Pythonでいうeggです。

peg

github.com

PEG(Parsing Expression Grammar)のRust実装。
PEGって知らなかったんですが、RubyのTreetopもそうだったみたいです。
正規表現に感覚は近いですが、再帰的なマッチャの定義ができるのがミソ。

Rustを拡張したDSLこんな感じで書くことでパーサーとグラマーが同時に実装できます。便利。
代数的データ型であるEnumとの相性がよく、リテラルに対してのマッチを除いた殆んどがASTの定義に即した記述になりました。
ここら辺、型定義からパーサを生成する何か?を作れそうな夢が広がります。

Rustを拡張したDSLコンパイル時に評価するために、pegはコンパイラプラグインを提供しています。ここがNightlyでないと動かない理由。

docopt

github.com

docoptはPythonやGoでよく使われるコマンドラインツールを作るためのパッケージで、ドキュメントからフラグやサブコマンドのパーサを生成してくれるナイスなやつです。
こちらもドキュメントからパーサを定義する処理をコンパイル時に走らせるため、コンパイラプラグインとして提供されています。

やったこと

四則演算子、変数、多項式、変数定義のための環境、変数に結びつく値の遅延評価

やってないこと

ブロック、比較、条件分岐、マサカリを防ぐためExecuterという名前をEvaluaterに変える(フセゲナカッタ

やりたいこと

型の静的検査

いまのところ苦労した点

多項式のパース

ここから多項式のパーサ定義なのですが、苦労ポイントでした。
単に数字リテラルのみの多項式ならさくっと書けたものの、項にもまた変数やブロックの評価といった式を書けるようにするのにハマった時間が長かったです。

結局、式の優先順位を正しく整理できていなかったのが原因で、単に

3

といったコードも Add(Mul(Pri(Atom(Ltr(Num(3)))))) のように構文木を作らなければならないことに気付きました。

Rustの変数のとりあつかい

Rustでなにか書く時に毎回ハマってる気がする。きのせい?

環境、Envを各executeメソッドで持ち回す方法でもハマりました。
Envは変数の名前とそれに紐付く式のハッシュマップを持っていて、名前が見つかれば式を評価した値で置き換えるために持ち回します。

spectr/executer.rs at 9dcc2061ef23cf653cc8c40fac25abb9f02ac76c · rail44/spectr · GitHub

枝を複数持っている構文木のノードでは、それぞれに対してexecuteを呼びだす必要があるため、当初は fn execute(self, &Env)といったメソッドの定義でした。
この&EnvはBorrowed Pointerと呼ばれるもので、変数への参照を"貸しだし"、それを渡した関数やメソッドが終了次第開放されるというものです。(Lifetimeに関する説明は省きます)

ただ、今回作ろうとしている言語は、式は必要になったタイミングで遅延評価され、同じ環境で同じ式は1回しか評価されないという戦略を取ろうとしていたのがアダになりました。
遅延評価をしたタイミングでEnvの持つ別のハッシュマップへとその値を格納する必要が出てくるので、Envへの変更権限を持たない&Envの持ち回しでは実現できなかったのです。

そこで、execute関数へとEnvを所有権ごと値渡ししてしまうことにしました。executeはEnvを返すことで所有権を返還する、といった方法です。

let (hoge_value, env) = try!(hoge.execute(env));
fuga.execute(env).map(|fuga_value| (hoge_value, fuga_value))

ちょっと不恰好に見えますが、&mutを使うよりも見通しが効きますし、ここら辺のオーバーヘッドを気にするのはrustcの仕事で、僕の懸案ではないと割り切りました。

まとめ

Rustむずい(小学生並みの感想) 言語開発たのしい(小学生並みの感想)

明日、12日目は@takeru911の担当です。お楽しみに!

ISUCON2015で仕込んでいたネタ(h2o + mruby)の供養をする記事

ISUCON2015予選お疲れさまでした。
予選関連エントリとしてはかなり出遅れてしまいましたが、自分がISUCONに向けて仕込んでいたものの、日の目を見なかったネタの供養をしたいと思います。 燃えろよ燃えろ。
(自宅でBeagleBoneBlackつかって動かしてたブログが消滅してたのでこっちで書くジャバよ。)

参加チーム

アルパカ三姉妹 です。パカー。
ISUCONへの参加が初めてなのは自分だけだったので、足を引っぱるまいと気負いが凄かったと反省。

仕込んでいたもの

以前からmatsumoto-rさんのブログエントリ(その1, その2)によって、試したい気分が高まっていたh2oのmruby拡張をネタに一発逆転を目論み、sinatraアプリの一部ルーティングをmrubyで書けるような何かを作ろうとしました。
結果、

github.com

github.com

の2つのmrbgemsと、これら+sinatraアプリを移植するために必要そうなmrbgemsを組みこんだh2oのフォーク

github.com

を作りました。

mruby-r3

Cで書かれた高速なルーティングライブラリR3バインディングです。
今回に必要そうなものしか書いていません。

mruby-rack-r3

最近、h2oのmruby拡張のインターフェースがrack準拠になっていたため、includeすることでsinatraライクなDSLを使えるようにするモジュールです。
mrubyオンリーのmrbgemsです。

それぞれREADMEをご覧ください。

なぜ使わなかったか

アプリケーションコードの改善で沼に嵌ってしまい、余裕がなかった。
(それでも使ってみればよかったのでは???????)

ベンチマーク

これだとコード達が浮かばれないのでベンチマークを取って供養します。
ベンチマークに使った環境はGCEのn1-highcpu-16、ubutu-15.04です。

https://github.com/rail44/h2o/tree/mrub-rack-r3github.com

↑のブランチ上で

./install_deps_ubuntu.sh
cmake -DWITH_MRUBY=ON .
make h2o

とすることで、mruby-rack-r3とおまけにmruby-redisが同梱されたh2oバイナリが作成できます。

./h2o -c examples/h2o-mruby/h2o.config

とすることでベンチマーク用のサンプルアプリケーションが立ち上がります。 アプリケーションコードはこんな感じ。

class App
  include Rack::R3

  def redis
    @redis ||= Redis.new('127.0.0.1', 6379)
  end

  get '/hello/{first_name}/{last_name}' do |f, l|
    [200,
     {'content-type' => 'text/plain; charset=utf-8'},
     ["Hello #{f} #{l}!\n"]
    ]
  end

  get '/incr' do
    [200,
     {'content-type' => 'text/plain; charset=utf-8'},
     ["#{redis.incr('hoge')}\n"]
    ]
  end
end

App.new

また、これと同等なsinatraアプリケーションを rubyapp_for_bench に書きました。
h2oの並列ワーカー数4に対し、pumaの並列ワーカー数16、最大スレッド16と大きく取ることで、pumaのデーモンが足らなくなることを防いだ上で性能を比較してみます。

パラメータとして受けとった文字列を結合するエンドポイント

satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/hello/Satoshi/Amemiya
HTTP/1.1 200 OK
Date: Thu, 01 Oct 2015 08:19:57 GMT
Server: h2o/1.5.0
Connection: keep-alive
Content-Length: 23
content-type: text/html;charset=utf-8
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

Hello Satoshi Amemiya!

に対し、ベンチマークツールwrkを使ってみます。

sinatra

Running 30s test @ http://localhost:8000/hello/Satoshi/Amemiya
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.79ms    3.36ms  58.19ms   75.05%
    Req/Sec     5.54k   397.66     8.34k    83.42%
  662753 requests in 30.09s, 174.45MB read
Requests/sec:  22027.56
Transfer/sec:      5.80MB

mruby-rack-r3版

Running 30s test @ http://localhost:8000/hello/Satoshi/Amemiya
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.23ms    5.15ms 202.99ms   99.81%
    Req/Sec    24.25k     1.89k   38.18k    64.78%
  2905104 requests in 30.10s, 559.65MB read
Requests/sec:  96514.58
Transfer/sec:     18.59MB

はやいo(^^)o

Redisへのアクセスが発生するエンドポイント

satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/incr
HTTP/1.1 200 OK
Date: Thu, 01 Oct 2015 08:23:21 GMT
Server: h2o/1.5.0
Connection: keep-alive
Content-Length: 1
content-type: text/html;charset=utf-8
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

1⏎                                                                                                                                                                                               satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/incr
HTTP/1.1 200 OK
Date: Thu, 01 Oct 2015 08:23:23 GMT
Server: h2o/1.5.0
Connection: keep-alive
Content-Length: 1
content-type: text/html;charset=utf-8
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

2⏎

sinatra

Running 30s test @ http://localhost:8000/incr
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.12ms    3.56ms  69.91ms   74.28%
    Req/Sec     5.20k   425.08     7.68k    75.83%
  621764 requests in 30.06s, 152.88MB read
Requests/sec:  20684.90
Transfer/sec:      5.09MB

mruby-rack-r3版

Running 30s test @ http://localhost:8000/incr
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.32ms    2.68ms 199.00ms   99.95%
    Req/Sec    11.06k     1.39k   14.51k    58.72%
  1325187 requests in 30.10s, 234.01MB read
Requests/sec:  44026.30
Transfer/sec:      7.77MB

早いものの、先ほどのエンドポイントに比べ、速度の向上具合が緩やかになっています。
ここからは、今後時間ができたときにじっくり考えたい内容ですが、mrubyのコード中でredisへのアクセスが発生した際にh2oがそのスレッドを開放できず、イベントループの恩恵が得られてないのではないかと仮説を立てています。
(どうやって確かめればいいのだろう)

まとめ

ISUCONく"や"し"い"

日本語入力環境SKKを使う

今まで、日本語入力にはibus-mozcを使っていたのですが、ふとSKKを使ってみたくなり早速チャレンジしてみました。
理に適っているのは分かるんですが、今までと全く違う入力方法に戸惑っています。なので、入力の練習も兼ねて環境の作成手順をブログに書くことにしました。

linuxの場合はibus用のモジュールがあるのでインストールは簡単。ibus-skkとかそういう名前のパッケージを入れれば大丈夫でした。
他のOSでも使う方法があるみたいなので……ググってください。

で、使ってみたんですが、!マークがひらがなモードの時も半角になってしまいます。
調べてみると、~/.config/ibus-skk.jsonを編集すれば定義を変更できるという文献がいくつか見つかったんですが、ibus-skkの仕様変更で変換プログラムはlibskkとして分離された様子。

ではどうするかと言うと、~/.config/libskk/rules/以下に新しくプロファイルを作ればいいみたい。
参考

mkdir -p ~/.config/libskk/rules
cd ~/.config/libskk/rules
cp -r /usr/share/libskk/rules/default ./user

デフォルトのプロファイルをそのままコピーして編集することにします。
コピーしたディレクトリの中には2つのディレクトリと1つのJSONファイルがあり、それぞれ

  • keymap
    • 入力キーと動作の対応
  • rom-kana
    • かなモードの時の変換定義
  • metadata.json

です。 まず、metadata.jsonを開くと、

{
    "name": "Default",
    "description": "Default typing rule"
}

こんな感じになってるので、nameを"User"、descriptionを"My typing rule"とかに書き換えます。

後はrom-kana/default.jsonを色々弄って、ibus-skkの設定画面からUserプロファイルを指定してやれば設定が反映されます。
(ibusの再起動を忘れずに!)
僕は、keymap/hiragana.jsonを編集して、ひらがなモードの時の他のモードへの切り替えを無効化したりしました。

これを書いてる間に大分SKKに慣れて来た感じがあります。手書きで文章を書くような変換が持ち味で、タイピングが非常に心地いいです。是非試してみてください!