go-sql-driver/mysql の v1.3 が出たよ

リリース日が去年の12月なんでもう時間経っちゃってるんだけど、一応日本語でアナウンスしておきます。

MySQLドライバの go-sql-driver/mysql が、 Go 1.8 の新機能サポートの前に安定版リリースしようということで、 v1.3 のタグが打たれました。

master ブランチに昨日 (2017-03-23) から 大きい変更 をマージし始めたんで、glide などの依存管理ツールを使ってる人は、プロダクション/ステージングでは v1.3 に固定しておくことをお勧めします。 (それ以外の人はむしろ master ブランチでテストしてくれると嬉しい。)

Windows では2020年を待たずに Python 2.7 が使い物にならなくなっていく

昨日 mysqlclient 1.3.10 をリリースしました。 今までは Windows 版の wheel は Python 2.7 だけに提供していたのですが、 1.3.10 からは 3.5 と 3.6 だけに提供して 2.7 はドロップしました。

そもそも今まで Python 3 に wheel を提供できてなかったのは、 MySQL Connector/C の VC14 (VS2015) に対応したライブラリが提供されておらず、 Python 3.5, 3.6 は VC14 でビルドされていて VC12 用のライブラリにリンクすると大量のエラーでるわ自分で手順読みながら頑張って MySQL をソースからビルドしてもなんか動かないわで諦めてたからです。

それが、2年待て、よーーーやく MySQL Connector/C 6.1.9 から VC14 のライブラリが同梱される用になりました。 GPLだから困ったら自分でなんとかしろと言われればそれまでなんですが、もうちょっと早くできなかったんでしょうか、Oracleさん。。。

一方で、 Python 2.7 は VC9 (VS2008) でビルドされています。しばらく前の(具体的なバージョンは忘れた) MySQL Connector/C から、 vc10, vc11, vc12 用のライブラリしか提供されてなかったのですが、 vc10 と無理やりリンクするとリンクエラーも起こらずちゃんと動いてるっぽかったし、特にライブラリ側でmallocしたメモリをPython側でfreeするみたいな明らかな問題も無かったので、vc9とvc10のランタイムライブラリを両方リンクすることでなんとか wheel を提供できていました。

それが、 Connector/C 6.1.9 からは vc11, vc12, vc14 のライブラリだけが提供されるようになり、 vc10 のライブラリが消えました。 一応、 utf8mb4 に対応した後、 6.1.9 以前のライブラリバージョンを使ってビルドし続けることもできるんですが、もともと気持ちよくない構成でビルドしてたので思い切って Python 2.7 wheel のビルドをやめることにしました。

Windows でバイナリ互換性を維持するには VCRT のバージョンを固定する必要があり、 Python 2.7 は 2008 年の VCRT にずっと依存しています。 Python コア開発者は 2020 年まで Python をメンテしていますが、サードパーティーのライブラリは 2020 年まで 2008 年の VCRT をサポートしてくれるわけではありません。 VC++ 2013 や 2015 に移行したライブラリは、 VC++ 2008 では利用できない機能 (<stdint.h> とか) を喜んで使うでしょう。古いバージョンのライブラリを使い続けるのにも、セキュリティ fix をバックポートする努力をしなければ、安全でないものを配布することになります。 (Python 2.7 サポートを望む声に負けて、安全でないライブラリを配布してるライブラリがすでに存在するかも知れません。というか存在すると思います。)

Windows 向けに binary wheel を提供している開発者は、そろそろ Python 2.7 のサポートのために変に頑張るのを止めて、代わりにユーザーに Python 3 への移行を頑張ってもらうべきだと思います。

WindowsPython 2.7 を使っている開発者は、 pip install が成功するからという理由で Python 2.7 を使い続けるのをやめましょう。 「Python 2.7 サポートのためにバグやセキュリティホールのある古いライブラリが使われてないか」をチェックし続ける努力より、 Python 3 に移行する努力のほうが建設的ですよ。

pip 9.1 から msgpack が使われるようです

Adopt cachecontrol 0.12.0 with msgpack support というコミットがありました。

どうやら CacheControl というのが pip が使っている requests 用のキャッシュライブラリで、その最新版が msgpack を使っているようです。

前のバージョンはバイナリデータを base64 した上で json に入れて gzip していたのですが、もともと圧縮されてるバイナリを扱うときに gzipbase64 によって増えた分を減らす以上の効果は期待できない上、 PyPI からダウンロードするファイルってほぼ100%圧縮済みなので、キャッシュファイルの読み書きで無駄なオーバーヘッドがあったみたいですね。

バンドルされてる msgpack は pure Python で実行できる fallback モジュールのみなのでどこでも動くし、 Cython 版じゃないのも今回みたいにファイルが大きい以外はデータは多くなくて単に base64 + gzip の負荷を減らしたいときは完全に安全だし、まさに msgpack の典型的な成功例 (jsonバイナリ詰めたかったら msgpack!) のように見えます。

ということで、 pip 9.1 がリリースされたら msgpack のユーザーが爆発的に増えそうです。

処理系ベンチマーク環境としての Azure VM vs EC2 vs GCE

ISUCON の練習で、Azureの「開発者プログラム特典」という1年間限定で毎月3000円の無料枠を使い始めたのと、副賞でさらに無料枠を貰えそうなので、最近一番 IaaS を使ってる Pythonベンチマーク環境として Azure も使ってみています。

ラップトップPCだけで生活してる人が、時間の掛かるベンチマークをするためだけに IaaS を時間借りするというニッチな視点で、既に使った経験のあるEC2, GCE と Azure を雑に比べてみます。

Hyper Threading

EC2 と GCE は Hyper Threading が有効です。 EC2 の c4 系も、 GCE の n1-highcpu 系もコア数は偶数になっています。両者とも汎用系だと HyperThreading の仮想1コアのマシンが作れるみたいなので、間違ってもベンチマーク用途でそういったインスタンスを使わないようにしましょう。

一方 Azure は Hyper Threading が無効みたいです。CPU重視の F や Dv2 系インスタンスも1コア単位です。

性能の安定性

2回 pyperformance run -b 2to3 した結果です。インスタンスガチャはしてないので、絶対性能は無視して安定性に注目してください。

Azure VM (F1):
Median +- std dev: 622 ms +- 17 ms
Median +- std dev: 644 ms +- 21 ms

EC2 (c4.large):
Median +- std dev: 537 ms +- 4 ms
Median +- std dev: 540 ms +- 4 ms

GCE (n1-highcpu-2):
Median +- std dev: 819 ms +- 7 ms
Median +- std dev: 817 ms +- 7 ms

Azure が 3% 程度、EC2とGCEは1%程度のばらつきがあります。 インスタンスの停止&起動を繰り返していないので断言できませんが、 Azure は EC2 より性能が安定しない感じがしていたので、この結果通りなんじゃないかなと思います。

3% のチューニングを10回すれば25%以上の性能向上になるので、ベンチマークの誤差が3%あるのはかなり痛いです。

その他

Azure VM

Azure は固定IPをしなくても固定DNS名を用意してくれるので、固定IPを買ったりDynamicDNSを設定したりsshの設定を毎回切り替えたりしなくても良いのがとても便利です。

新規マシンの作成はちょっと遅い気がしますが、停止・起動はEC2と同程度な気がします。 時間指定してシャットダウンするオプションを管理コンソールから簡単に設定できて停止忘れ対策になるのがとてもうれしい一方、 ssh してる状態で shutdown -h コマンドで停止してもインスタンスが開放されず課金が続くのは若干面倒。

あと、SSDがGB単位で使えないのが痛すぎます。一番小さい P10 で128GBです。

EC2

インスタンスガチャをしてみないと分かりませんが、Pythonベンチマークではマルチコアを有効に使えないので、コアあたりの性能が良さそうな EC2 c4 インスタンスが一番魅力的です。

ちょっと奮発して c4.8xlarge を使えば、 Turbo Boost を切ることができてさらにベンチマークの安定性が増します。

一方、新しいインスタンスVPCから作ろうとすると、 ssh できるようになるまでにネットワーク周りで設定しないといけないことが多くて手順覚えてられないのが難点。

GCE

管理画面があっさりしていて、仮想マシンを作るのがとても楽です。ウィザードに従って設定しても、 「gcloud コマンドでこれをするには」がコピペできるので、コマンドの使い方を調べる手間が要らないのと、そのコマンドを使うための環境をローカルに用意しなくても Cloud shell に造ってしまえるのがとても楽。

あと、プリエンプティブインスタンスが停止されてもデータが消えないでまた起動して続きからできるのがとても楽。EC2のスポットインスタンスはちょっと新規インスタンス立てて何かのベンチマークを実行しようってときに使う気にならないけれども、GCEだと積極的に使える。

結論

すごく重要な点として、どの仮想マシンも、CPUのパフォーマンスカウンタが見えません。キャッシュミスとかの統計が取れない。つらい。 できるだけ管理するもの(物理)を減らしてスッキリしたかったのですが、仕方ないので会社にラップトップと別に好きにいじれる物理PCを用意してもらうことにしました。

それさえなければ、大抵のCPUを使うプログラムのベンチマークは、シングルコア性能が良い EC2 c4 か、手軽な GCE で良いと思います。 そしてせっかく無料枠あるのに Azure を使うモチベーションが…何に使おう。

CPython の Core Developer になりました

Python 3.6 に取り込まれた dict の新実装などでコアコミッターに興味を持ってもらい、 Core Developer (要するにコミッター) に推薦しようか?という提案をもらいました。

最初はコミッターとか面倒そうだし、コミットメッセージとかNEWSエントリー(通常パッチをコミットするときにコミッターが書く)とかを英語で書くのも英語が得意な人がやったほうがいいだろうし、とりあえず github に移行するまでは様子見しておこうと思ってたのですが、 dict 関係のパッチがいくつもレビュー待ちでなかなかコミットされないのを見て「やっぱりアクティブなコミッターが全然足りてない」と考え直し、志願することに。

で、先月末にコミット権をもらった(というか push できる権限を持った hg アカウントに ssh 鍵を登録してもらった)のですが、新米コミッターは簡単なパッチでも他のコアコミッターのLGTMなしにコミットしちゃダメだよと釘をさされたので、結局レビュー待ち行列状態は変わらず。1週間以上経って今日始めてコミットを push できました。 (コミット)

コミットしたのは、会社のBlog記事 DSAS開発者の部屋:Python の dict の実装詳解 で言及していた次の問題です。

しかし、この方法ではハッシュテーブル内の密度の偏りの影響を避けられるものの、ハッシュ値の下位ビットが衝突する key は同じ順番に巡回するので線形探索になってしまい効率が悪いという欠点があります。そこで、先程の関数を改造して、次のようにしています。

    for (perturb = hash; ; perturb >>= PERTURB_SHIFT) {  
        i = i * 5 + perturb + 1;

これで、最初のうちはハッシュ値の上位ビットを使って次の位置を決定していき、ハッシュ値を右シフト仕切ったら先ほどのアルゴリズムで確実に巡回する、というハイブリッド型の巡回アルゴリズムになります。 (ちなみに、このforループには最初の衝突のあとにくるので、 perturb の初期値が hash のままだと、hash 値の下位ビットが衝突する key と1つ目だけでなく2つ目も衝突してしまいますね…)

例えば要素数が5以下の場合、ハッシュテーブルの大きさは8なので、5要素中3要素のhashの下位3bitが同じだった場合に、2段階のコンフリクトが発生していました。これが最初の衝突後すぐにハッシュのより上位のビットが使われるようになって、その上位bitが異なれば衝突は最初の1回だけで済むようになった形です。とてもレアケースだと思うので実際のアプリで計測誤差以上の速度差は出ないでしょう。

これからも他人のパッチを自分のLGTMだけでコミットできる真のコミッタになることを目指して気長に徳を積んでいきます。

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