Thinkpad X250 に Ubuntu デュアルブートで遊ぶ

ふるさと納税で X250 が数量限定、寄付額13万円で出ていたので衝動買いしてしまいました。 CPUはかろうじて Celeron ではなく Core i3 ですが、メモリ4GB, WiFi は 5GHz 未対応、500GB HDD、液晶は TN の HD (縦768px) というスペックです。

HDDは256GBのSamsungのV-NAND使ったSSDに換装して、 Windows 10 と Ubuntuデュアルブート化し、 Ubuntu 15.04 メインで遊んでみます。 (15.04 からタッチパッド周りが改善されているようなので 14.04LTS より最新のバージョンを選びました)

/boot は ext4 にしような

あとで Windows を消して拡張しやすいように、 Windows の直後を / にし、その後ろに 10GB の swap を残すというパーティション構成にしました。 swap が 10GB なのは、ハイバネートに swap があった方が良かった記憶があって、将来メモリを8GBに増設する可能性を考えて余裕を持って 10GB にしました。

インストール時にちょっと遊びココロで / を xfs にしてみたのですが、 grub のシェルが立ち上がります。 シェルの使い方全くわからないままなんとかググって対処してみたところ、 grub が xfs を読めないみたいです。 あきらめて ext4 で再インストールしたら今度はなんの問題もなくデュアルブートできました。

一応 BIOSの設定画面でセキュアブートは切っていましたが、それ以外は特に問題なく。 Mac 以外でEFIブートが初めてだったけどチョロい。

バッテリーを長持ちさせたい

リチウムイオン電池は満充電だと寿命が縮むのは常識ですよね。 ThinkPad も昔から満充電しない制御を設定可能で、しかも Linux からも設定できるのがメリットでした。

それが、なんか Windows 10 だとそのツールがもう存在しなくて、設定できなくなっていました。 ググったところ、 Store アプリの古いバージョンで可能みたいですが、古いバージョンを探してインストールするのダルいし、 Windows はあまり使う気がないので放置。

Ubuntu では tp-smapi-dkms をインストールすればいいやと思っていたら、どうもカーネルモジュールがロードできない。 ぐぐってみたところ、今は TLP という電力管理ツールで設定が可能なようです。Ubuntu 15.04 は ppa:linrunner/tlp を使って、 15.10 からは標準で利用可能なようです。

その他感想

今時 WiFi が 5GHz 対応してないのも完全に予想外でしたが、何よりキツイのが液晶。明るさが足りないのかコントラストが足りないのかわからないけど、白地に黒文字のときに、暗いプロジェクターで映された文字を目を凝らして読んでいるような、古い液晶を使っているような感覚です。 黒字に白文字だとあまり気にならなくなるので、ターミナルに閉じこもってハックする効率は上がるかも?

あと、せっかく横幅あるのに、キーボードの左右にスペースがあってキーボード自体は @ や [ キーが細くなってるのがもったいない。Mac と行き来するときに感覚が狂って良くない。

試しに PC DEPOT の中古買取の見積もり出してみたら新品の場合の上限が21k円と言われてしまったので、X250の液晶交換の情報がもっと増えてきたらIPS液晶への換装を試してみます。

そこそこ薄くコンパクトになのに、 9mm の HDD が載せられたり、 HDD + M.2 SSD のデュアルディスクができたり、メモリ交換できたり、液晶交換もできそうだったり、なかなか面白いノートPCです。

VirtualBox 5 で利用可能になった Paravirtualization 機能 kvmclock を使う

Oracle に買収されてからずっと 4.x のまま大きな進化が無いのではないかと心配した時期もありましたが、とうとう VirtualBox 5 が出ましたね。 買収後は vagrant や、最近だと boot2docker などで重要度が大きく増しているので、今後の発展にも期待しています。

さて、 VirtualBox 5 の目玉の新機能といえば Paravirtualization 機能がいろいろな所で表に出てますね。これって何なんでしょう?

一般論を言えば、ホストマシンがハードウェアをエミュレートするのが HVM、ゲストマシンにAPIを提供するのが Paravirtualization (PVM) なので、CPU等の仮想化支援機能を使える場所を除いて一般的には PVM の方が高速化・効率化が可能です。

VirtualBox でも 4.x 時代からNICが virtio に対応していました。ネットワークインターフェイスの種類として virtio を利用すれば、実在するハードウェアのNICエミューレートするために無駄なコストを払う必要がなく、すこし軽くなるはずです。

ということは、 VirtualBox 5 で何か PVM のデバイスが追加されたはずです。(個人的にはブロックデバイスが virtio になって高速化されると嬉しいなと期待していました。) 調べてみたところ、ドキュメントにちゃんと載っていました。

10.4. Paravirtualization providers

KVM: Presents a Linux KVM hypervisor interface which is recognized by Linux kernels starting with version 2.6.25. VirtualBox's implementation currently supports paravirtualized clocks and SMP spinlocks. This provider is recommended for Linux guests.

Paravirtualization Provider に KVM を指定すると、 clock と spinlock の PVM が利用できるようです。

このうち clock については、過去に VirtualBox にマルチコアを割り当てると gettimeofday 等のシステムコールが非常に遅くなるという問題にぶつかったことがあり、最近でもよく EC2 などでマルチスレッドプログラムのベンチマークをするときは clocksource を xen から tsc に変更してるなど、個人的に非常に分かりやすく効果がありそうなので、試してみました。

まず、 Paravirtualization Provider に KVM を指定して (GUI では System > Accelaration にあります) Ubuntu 14.04.2 を起動します。

$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
kvm-clock tsc acpi_pm
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
kvm-clock

kvm-clock が利用可能で、デフォルトで利用されていることが解ります。

次に、以下のプログラムで gettimeofday の速度を計測してみます。

#include <sys/time.h>
#include <stdio.h>

int main()
{
    struct timeval tv;
    for (int i=0; i<1000000; i++) {
        int r = gettimeofday(&tv, 0);
        if (r < 0) {
            perror("gettimeofday");
            return 1;
        }
    }
    return 0;
}
$ gcc -O2 -o t t.c -std=c99
$ time ./t
./t  0.05s user 0.00s system 99% cpu 0.058 total

非常に速いですね。 tsc のように、実際にはシステムコールを発行しないようです。 strace しても gettimeofday は確認できませんでした。

tsc と速度を比較してみます。

$ sudo sh -c "echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource"
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc
$ time ./t
./t  0.02s user 0.00s system 95% cpu 0.023 total

流石に tsc よりは倍くらい遅いのかな。最後に acpi とも比較してみます。 VirtualBox 4 ではマルチコアだとすごい遅かった覚えがあるのですが、さて。

$ sudo sh -c "echo acpi_pm > /sys/devices/system/clocksource/clocksource0/current_clocksource"
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
acpi_pm
$ time ./t
./t  0.00s user 1.64s system 99% cpu 1.645 total

gettimeofday 1回あたり 1.64μs. VirtualBox 4 で計測してなかったので比べられないけど、速くなってるかもしれません。

とはいえ、 kvm-clock と比べて30倍、 tsc と比べて80倍遅いので、 gettimeofday() 等のクロック系システムコールを多用するプログラムを動かす場合は避けたいところです。

クロックソースについてはよく知らないので思い込みですが、tsc がCPUのベースクロック(あるいはそれに類する、同期回路を動かすためのクロック) を利用している分、実時間を計測するには不安定(たぶん1日当たりの誤差は1000円の腕時計の方が小さい)なのに対して、 kvm-clock だと自動的にホスト側の時計に同期してくれることを期待しています。

tsc に比べたら倍くらい遅いと言っても、もともと tsc を使った時の gettimeofday が他のシステムコールに比べてタダ同然だったので、気にせず kvm-clock を使って置けば良さそう。

Python の新しいプロファイラ vmprof が面白い

PyPy 2.6 と同時に、 vmprof という CPython/PyPy 用のプロファイラが登場しました。

私はまだ PyPy では使っていませんが、CPythonのプロジェクトをこれでプロファイル取ってみたらなかなか面白かったので紹介します。

概要

Python にはもともと標準ライブラリとしてプロファイラ (cProfile) が付いてきていますが、これは関数の呼び出しと戻りでコールバック関数を呼び出しつつ実行時間を計測するタイプのプロファイラで、短時間でも正確なプロファイルが取れる反面、オーバーヘッドが大きく、小さい関数をたくさん呼び出す部分がオーバーヘッドでより大きく見えてしまうなどの問題がありました。

これと別の種類のプロファイラとして、定期的にサンプリングして、サンプルが多いところが実行時間も多いハズ、というプロファイラもあります。こちらはある程度の量のサンプルを集めないと正確性にかけるものの、オーバーヘッドが小さく、小さい関数をたくさん呼び出す場合も正確にプロファイルができたり、本番環境のサーバーのプロファイルを取ることができたりします。 Go 言語の pprof もそうですし、 Python では私も昔に sigprofiler というものを作ったことがあります。

vmprof もこのサンプリング型のプロファイラです。 PyPy だときっと JIT の状態が見れるなどの特長があるのでしょうが、私は今のところ CPython で使っています。 CPython 用としてみた場合の vmprof の特長は次のような感じです。

  • Pythonのスタックと同時にCのスタックも保存して、同時に見ることができる
  • Python スタックを取る部分も C で実装されており、 sigprofiler よりずっと軽い

vmprof は Python スタックを取るために、 PyEval_EvalFrameEx をトランポリン関数で置き換えます。 SIGPROF シグナルハンドラでCのスタックを解析するのですが、その際にトランポリン関数を見つけるとその引数である frame オブジェクトを見つけて、 frame オブジェクトから Python のスタックを取ります。

(ちなみに、 CPython 用の部分とそれ以外が綺麗に分かれているので、他のインタプリタ型言語でも関数呼び出しの際に呼ばれる関数をセットしたら vmprof が使えるはずです)

C の関数呼び出しは Python の関数呼び出しよりずっと軽いので、トランポリン関数が1つ挟まるだけならほとんどオーバーヘッドはありません。

vmprof-server

コマンドラインでも簡単な出力を見れますが、 vmprof の本領を発揮するにはビジュアライザである vmprof-server が必要です。 これは Django REST framework とシングルページアプリケーションの組み合わせになっています。

他人に見られてもいいなら デモサイト が利用できますし、見られたくないなら自分で簡単にセットアップすることができます。

$ git checkout git@github.com:vmprof/vmprof-server.git
$ cd vmprof-server
$ pip install -r requirements/base.txt
$ ./manage.py runserver

これを使って、次のようなプログラムのプロファイルを取ってみましょう。

import time

def sum1(n):
    return sum(range(n))

def sum2(n):
    return sum(range(n))

def sum12(n, m):
    z = 0
    for _ in range(1000):
        z += sum1(n) + sum2(m)
    return z

def foo():
    return sum12(10000, 100000)

def bar():
    return sum12(30000, 10000)

def main():
    t0 = time.time()
    while time.time() < t0 + 10:
        foo()
        bar()

if __name__ == '__main__':
    main()
$ python -m vmprof -n --web http://vmprof.baroquesoftware.com/ ex2.py

デモサイト

f:id:methane:20150609223332p:plain

このプログラムで、 sum12() は sum1() と sum2() を呼び出していますが、その呼び出し比率は引数によって変わります。そして foo() と bar() が sum12() を呼び出していますが、引数が違うのでその内訳が違うはずです。

vmprof と vmprof-server は sum12() の中身の内訳をまとめてしまわず、ちゃんとスタックを維持してビジュアライズしてくれるのがわかると思います。

例えば ORM を利用している場合、クエリをコンパイルしてる部分と、オブジェクトをフェッチする部分の重さの比率を見ることで、たくさんオブジェクトをロードしてるから重いのか、複雑なクエリをコンパイルしているから遅いのかを判断することができます。

まだまだ未完成なプロジェクトですが、わかって使えばとても強力なので、使ってみて足りない部分にプルリクエストを送ったりして応援していきましょう。

PyMySQLのメンテナにSQLAlchmey開発者のMike Bayer (@zzzeek)さんが参加しました

このブログでも数回紹介していたとおり、今メジャーな PythonMySQL ドライバ3つのうち2つ (MySQL-python の fork の mysqlclient と PyMySQL) を僕がほぼ単独でメンテナンスしている状況でした。

メンテナンスしているといっても、両方とも MySQL-python との互換性を第一に掲げているので、 Python 3 対応が終わった後はほとんど進化は無くて、淡々とバグの修正を積み重ねては1年に1度以上リリースするという程度です。

実際には Python では PostgreSQL 周りに比べて MySQL 周りは遅れていて幾つか改善案はあったのですが、子育てや他にも Python でやりたい事があったり仕事でも Go で楽しんでたりして手を付けられていませんでした。

そんな状況の中、 Mike さんが PyMySQL-Users ML に突然投稿をされました。 OpenStackの開発でPyMySQLを利用する事を検討していて、問題点・改善点が上がっているんだけど、PyMySQL側にプルリクエストなどを受け入れる準備があるかどうかを質問するものでした。

もともとmysqlclientとPyMySQLも個人でメンテナをするべきでないと思っていましたし、MikeさんはPythonで一番高機能なORMであるSQLAlchemyの開発者、そしてOpenStackといえば今Pythonで最も活発なプロジェクトの1つと呼べる巨大プロジェクトです。このチャンスを逃す手はありません。すぐにGithubのPyMySQL Organization に追加することを提案し、快諾してもらいました。

PyMySQLは性能や非同期対応についてのアイデアがあって、これはまさに OpenStack でも求められているものなので、これからOpenStackコミュニティと協力して進化させていこうと思います。

一方 mysqlclient は、最近 MariaDB が非同期APIを追加した MySQL Connector/C をリリースしているので、それに対応させる手段を検討していたのですが、ビルド時に上手く検出できるのか、PyPyでの速度を上げるために ctypes か cffi を使う別プロジェクトを fork するべきか、その場合 CPython では速度低下するけどどうするか…といった問題があり、メンテナンスコストと両立する解決策が見つかってなかったので、これを機に考えるのをやめることにします。

json.dump() は ensure_ascii=False の方が遅かった

Python の標準ライブラリの json モジュールのエンコード機能には ensure_ascii というオプションがあり、デフォルトでは True です。このオプションが真のときは、非ASCII文字を全て "\uXXXX" の形にエスケープします。

ensure_ascii=True の方が安全ですが、DBにjsonシリアライズしたデータを突っ込むなどの用途ではFalseの方がコンパクトになるし、日本語等を含む場合にエスケープされずに読みやすいので、 ensure_ascii=False を多用していました。

ensure_ascii=False はエスケープ処理が減る分速度も向上すると特に実験もせずに信じていたのですが、社内のプロジェクトで ensure_ascii=True の方が圧倒的に速いという報告を受けて、確認してみたら確かに ensure_ascii=True の方が速かったです。

ソースコードを確認したところ、 ensure_ascii=False の場合はエスケープ処理を正規表現の置換で行う一方、 ensure_ascii=True の場合は _json という speedup モジュールを使って高速に処理していました。

ensure_ascii=False の場合用の speedup 関数を追加するパッチを作って 報告 しておいたので、取り込まれれば Python 3.5 からは同等以上の速度になるはずです。

追記

コミットされました

Issue #23206: Make ``json.dumps(..., ensure_ascii=False)`` as fast as th... · ae9739e · python/cpython · GitHub

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。