今日覚えたNumPyのテクニック

今日覚えたNumPyのテクニックを紹介します。

numpy.stack で2次元配列を繋げて3次元配列を作る

1次元配列を繋げて2次元配列を作りたいような場合には numpy.vstacknumpy.hstack という関数があります。

a = numpy.array([1, 2, 3, 4])
b = numpy.array([2, 4, 6, 8])
c = numpy.array([3, 6, 9, 12])
numpy.vstack([a, b, c])

# array([[ 1,  2,  3,  4],
#        [ 2,  4,  6,  8],
#        [ 3,  6,  9, 12]])

2次元配列を繋げて3次元配列を作りたいような場合にはどのようにすればよいか知らなかったのですが,numpy.stack という関数があることを知りました。

a = numpy.array([[1, 2, 3], [4, 5, 6]])
b = numpy.array([[2, 4, 6], [8, 10, 12]])
c = numpy.array([[3, 6, 9], [12, 15, 18]])
numpy.stack([a, b, c])

# array([[[ 1,  2,  3],
#         [ 4,  5,  6]],
#        [[ 2,  4,  6],
#         [ 8, 10, 12]],
#        [[ 3,  6,  9],
#         [12, 15, 18]]])

条件が3つ以上ある場合の条件演算子を integer array indexing で実現する

条件演算子 x ? a : b のようなことを配列のすべての要素に対してやりたい場合には numpy.where を使うことができます。

x = numpy.array([0, 1, 0, 1])
a = numpy.array([1, 2, 3, 4])
b = numpy.array([5, 6, 7, 8])
numpy.where(x, a, b)

# array([5, 2, 7, 4])

条件が3つ以上ある場合にどうすればよいか知らなかったので,いろいろ考えました。最初に思いつくのは numpy.where をネストして無理やり実現する方法です。

x = numpy.array([0, 1, 2, 1])
a = numpy.array([1, 2, 3, 4])
b = numpy.array([5, 6, 7, 8])
c = numpy.array([9, 10, 11, 12])
numpy.where(x == 0, a, numpy.where(x == 1, b, c))

# array([ 1,  6, 11,  8])

この方法では場合分けの個数が変化する場合に対応できなくなってしまうので不便です。別の方法として NumPy の indexing を使う方法を教えてもらいました。

x = numpy.array([0, 1, 2, 1])
a = numpy.array([1, 2, 3, 4])
b = numpy.array([5, 6, 7, 8])
c = numpy.array([9, 10, 11, 12])
u = numpy.stack([a, b, c])

# array([[ 1,  2,  3,  4],
#        [ 5,  6,  7,  8],
#        [ 9, 10, 11, 12]])

u[x, numpy.arange(x.shape[0])]

# array([ 1,  6, 11,  8])

列方向のインデックスとして numpy.arange を渡してやるのが重要で,u[x, :] ではうまくいきませんでした。

もっと良い方法をご存じの方はご連絡下さい。

ISUCON7の予選を学生枠で通過しました

ISUCON7の予選に @brook_bach さん,@mayoko_ さんと「座るだけのコンテストってな〜んだ?」で参加しました。

@brook_bach さんの記事

brookbach.com

@mayoko_ さんの記事

mayokoex.hatenablog.com

チーム結成の経緯

準備

3人とも別の大学ということもありミーティングはすべてSlackで行いました。

事前準備として以下のようなことをやりました。

お役立ち情報や秘伝のタレをGoogle Docsにまとめた

3人とも普段からWebプログラミングをやっているわけではないので,MySQLのログイン方法やsystemdの使い方などから復習してGoogle Docsにまとめました。MySQLのデータのバックアップとリストアは一度は練習しておかないと本番で詰む可能性があります。プロファイルのために使用するツールとして mysqldumpslow, dstat, kataribe, wsgi_lineprof を使用することも事前に決めておきました。nginx.confmy.cnf は過去のISUCONの参加者のブログから設定項目を寄せ集めて作りました。設定ファイルの更新とミドルウェアの再起動を行うスクリプトも事前準備しておいたので非常に捗りました。

作業フローを整備した

去年のISUCON6に参加したときには本番サーバーでデバッグをしていて非常に効率が悪かったので,今年は改めようと思い,手元の開発環境でデバッグGitHubにpush → 本番サーバーでpullして sudo ./reload.sh という作業フローにしました。

GitHubではプライベートリポジトリを用意しなければならないので普通は課金が必要になってしまいますが,学生特権でプライベートリポジトリが使い放題なので活用させていただきました。

サーバーからプライベートリポジトリにアクセスする方法は以下のページを参考にさせていただきました。

azriton.github.io

Pixiv ISUCONを使って練習した

注意:Pixiv ISUCONのネタバレがあります。将来的に解く予定がある人はスキップ推奨!

Pixiv ISUCON を解いて練習しました。本番ではPythonの実装を使用する予定だったので methaneさんのPython実装 を利用させていただきました。

このPixiv ISUCONの内容が今回のISUCON7と非常に深く関わっていて,ソースコードの流用などで非常に助けられました。

私はAWSのアカウントを持っていないしGCPはクレジットカードの登録をしたことがないインフラ弱者だったのですが @brook_bach さんがよろしくやってくれました。

f:id:kujira16:20171024005814p:plain

f:id:kujira16:20171024005823p:plain

f:id:kujira16:20171024005621p:plain

f:id:kujira16:20171024010854p:plain

f:id:kujira16:20171024010933p:plain

f:id:kujira16:20171024010923p:plain

f:id:kujira16:20171024010944p:plain

f:id:kujira16:20171024011704p:plain

Ansible playbookを準備した

@brook_bach さんが全てよろしくやってくれました。

本番開始直前まで

@brook_bach さんの勘が冴えまくりでした。正直なところ,私は「1台構成だったらAnsibleとか別に要らなくない〜?」と思っていたのですが,用意してくれていたので助けられました。

f:id:kujira16:20171024011745p:plain

f:id:kujira16:20171024011838p:plain

f:id:kujira16:20171024012116p:plain

本番

13:00 〜 14:00

  • サーバーが3台あるという事実が判明する
  • ansibleでツールのインストールやデータのバックアップや公開鍵の登録などが動いている間にレギュレーションを読む
  • アプリで遊んでみるとAjaxが動作しているっぽい雰囲気だったのでChrome DevToolsでネットワークの動きを見てみる
    • GET /fetch でチャンネルごとの未読件数を取得して,未読があったら GET /messagechannel_idlast_message_id をパラメータにして問い合わせるとメッセージが取得できるという仕組みらしい
  • 手元のPCの ~/.ssh/config の設定を書いて ssh app1 とかでアクセスできるようにする
  • 初期構成でベンチマーク → app1だけだと4232点,app1 + app2だと6187点

14:00 〜 15:00

  • git pull && sudo ./reload.sh を3台に適用するのが面倒だという話になる → @brook_bach さんがtmuxのsynchronize-panesという機能で3台同時に操作できるようにしてくれる
  • MySQLの設定を @brook_bach さん,Nginxの設定を私が担当する
    • 静的ファイルの配信の設定もこのとき行った
    • 初期状態の nginx.confuser www-data; を消すと /var/lib/nginx/proxy にアクセスできないというエラーが出て,user www-data; を付けると /home/isucon/isubata/webapp/public にアクセスできないという問題が起こり,30分くらい時間を溶かす
    • sudo gpasswd -a www-data isucon で解決した
    • /var/lib/nginx の役割や,Nginxがどのユーザーで動いているのかについて復習が必要。真っ当な方法を誰か教えてください…
  • /icons がとても遅く,Pixiv ISUCONと同様にMySQLに画像データが入っていたので,Pixiv ISUCONをやったときと同じ方針で解決を試みる
  • @brook_bach さんと @mayoko_ さんにはそれ以外の箇所の高速化をしてもらう

f:id:kujira16:20171024014623p:plain

15:00 〜 17:00

  • /icons の高速化に取り組む
    • /icons を静的ファイルとしてNginxに配信させるためには,アイコン画像が投稿された時に全てのアプリサーバーにアイコン画像を配る必要がある
    • WebDAVのようなオシャレなものを使うという発想がなかったので POST /saveicon というAPIを生やして POST /profile が呼ばれた時に全てのアプリサーバーの POST /saveicon を呼び出すという方法で配ることにした
    • デッドロックする可能性があるので本当は良くない
  • @brook_bach さんと @mayoko_ さんが画像配信以外の箇所を高速化してくれる
    • 私は全く見ていないのでよく分からないが,チャンネルの未読件数を(総数−最後に読んだときの総数)で計算できるようにしてくれたらしい
  • いろいろトラブルがありながらもアプリの修正が完了して31067点になる。この時点では学生2位

f:id:kujira16:20171024015530p:plain

17:00 〜 19:30

f:id:kujira16:20171024015947p:plain

  • 若干マシにはなったが /icons がまだ遅い
    • Pixiv ISUCONで作った秘伝のタレで expires 24h; を設定しているにも関わらず,3分の1くらいのリクエストしか 304 で返せていない
    • ベンチマーク中にdstatを見てみると,ほぼ100Mbps使われていてグローバル向けの帯域を使い切っている
    • この時点で28万点くらい出しているチームがいたので,帯域を使い切らない方法があるはず
  • 画像ファイルを捏造して軽いファイルを返してみたり,JavaScriptを書き換えて画像をLocal Storageに保存してみようとしてみる → 静的ファイルの書き換えはベンチマークで弾かれました
  • とりあえず帯域を稼ぐためにDBサーバーでもNginxを動かしてみる → 34882点(なぜスコアが上がらないのかよく考えるべきだった)
  • 画像以外の箇所でN+1を消したりしていたら52186点が得られた
  • "nginx image cache" でググるCache-Control "public" を付けろという記事 が出てきたので付けてみる → スコア変わらず

19:30 〜 21:00

  • Gunicornやカーネルパラメータの設定をする
  • 再起動テストをする → サービスが立ち上がるまでにアクセスすると502が返る以外には特に問題なかった
  • ガチャを引きまくって終了。ベストスコアの55931点は再現しなかった

敗因と反省

  • Cache-Control "public" を付けて,かつファイルのタイムスタンプを合わせる(レスポンスヘッダのLast-Modifiedを合わせる)とCDNがキャッシュしてくれて304で返せるという環境を想定したベンチマークだったそうです
  • Cache-Control "public" についての情報に行き当たった時に,もう少し詳細に調査をしていれば気づいたかもしれません
  • 一般枠と比べるとかなり低いスコアでの学生枠通過ですが,CDNの設定経験がある学生なんて普通いないので大目に見てくださいw
  • とはいえ,Conditional RequestsはRFCで標準化されている範囲だと言われると何も言い返せません…

感想

  • 学生枠で参加できる最後の年だったので予選を通過できて嬉しいです
  • アプリの実装が凝っていて(Pythonでさえ400行くらい)準備がすごく大変だったと思います。運営チームの皆様ありがとうございました

GitHub リポジトリ

github.com

ほしいものリスト

以下のURLから私たちのチームに書籍を購入していただけると私たちのチームを支援することができます。

http://amzn.asia/jdJ1NRV

pyenv+Minicondaからdirenvに乗り換える

背景

pyenvを使っていた理由

Minicondaを使っていた理由

  • NumPyやSciPyをバイナリ形式でダウンロードしたかったから
    • Wheel環境が整備されていなかった時代はプロジェクト用のvirtualenvを作って pip install scipy をする毎にNumPyやSciPyのコンパイルのために手が止まってしまっていた

乗り換えようと思った理由

  • Python 2.6を使わなければいけないことが最近ではほとんど無くなったから(homebrewでインストールできるpython2.7とpython3で十分)
  • Wheel環境が整備されてきたのでpipでもバイナリ形式でダウンロードできるから

乗り換え先として試してみたもの

pyenv + pyenv-virtualenv

  • pyenvは残したままでMinicondaだけ捨てる方法
  • pyenv local を使うことで,ディレクトリを移動したときにvirtualenv環境を自動的に変更できる
  • 試してみると,なぜかシェルの動きがもっさりしてしまった(Enterキーを連打してみると分かりやすい。原因不明)
  • プロンプトに出てくるvirtualenv名を消す方法が分からないVIRTUAL_ENV_DISABLE_PROMPT を定義すればいいだけだった…

direnv

  • pyenvもMinicondaも捨てる方法
  • プロンプトに出てくるvirtualenv名を消すためには,source bin/activate するときに VIRTUAL_ENV_DISABLE_PROMPT という環境変数に空文字以外の値を入れておけばよい
  • pip install したときに /usr/local/bin が汚染されるのが怖すぎるので,デフォルトでvirtualenvを有効にするために以下の設定を .zshrc に書いている
if [[ -d "$HOME/.virtualenvs/default" ]]; then
  VIRTUAL_ENV_DISABLE_PROMPT=true source $HOME/.virtualenvs/default/bin/activate
fi
  • ディレクトリ移動時のvirtualenv環境を自動的に変更するには,以下の設定を .envrc に書いておけばよい
source $HOME/.virtualenvs/rime-python2/bin/activate