2017年の目標

量的な目標

ライブに10回行く

去年は2月末のシンフォギアライブで初めてライブを経験してから、結構な回数ライブに参加した。

ちょうど10回ですね(Updated 2016/01/18) EGOISTのライブも行ったので11回だった。楽しいので今年も同じくらいはライブに行きたい。

映画を10本見る

映画も積極的に見たいんだけど、拘束時間が長いとなんとなく気後れしてしまう。しかし映画に行かなかった時間で漫画を読んでいることはよくあり、結果的には映画見れるじゃんと思ったことが何度もあった。

小説を10冊読む

最近はめっきり小説を読まなくなってしまった。去年読んだのって伊藤計劃くらいなのでは……。さすがにどうかと思うので読む数を増やしたい。自分で小説的な物を書くと、文体があまりにも味気なくてつらい気持ちになるので引き出しを増やしたいというのもある……。

旅行に10回行く

国内8回、海外2回くらい?何を旅行と呼ぶかは難しいが、1泊以上して、かつ特定のイベントへの参加よりも多くの事をしたら良いということにする。


Webサービスを3個作る

不特定多数のユーザが利用することを想定したものに限る。

ゲームを1本作る

あんまり公開とか考えず、自分が楽しければよいくらいの気持ち。最近のゲーム技術を勉強しておきたい。

上2つは過程でいくつか内部ツールを作ることになるんじゃないかと思う。その個数もカウントすると面白そう。

GitHubに200 Contributionsする

200という数値に絶対的な意味はなくて、去年は160 Contributionsだったようなので、もうちょっとアクティブにコードを書きたいくらいの気持ち。

質的な目標

ICFPCで1位を取る

去年は2位だった。打倒Unagi。

Magic: the Gatheringをプレイする

去年の終わり頃に遊んで楽しかったので、情報を追いかけてみる。

はてなブログのデザインを直す

PCで読むと文字がでかくて気持ち悪いので、納得の行くCSSを書く。

ブログで目標のトラッキングをする

なんかブログというとある程度まともな文章を書かないといけない気持ちになってしまうが、本来自分のブログに何を書いたってとやかく言われる筋合いはない。Twitterレベルの雑な更新でもちゃんとブログに書いとくと検索しやすくて良い気がする。

聖地巡礼

この記事はドイツのトリアドベントカレンダー1日目の記事です。

公式サイト(繋がんないけど仕様なんですかね)に書いてある座標の様子です。

周囲を見渡すとこういう風景で、ちょっと座標ずれてるのでは?

西に行くとそれっぽい場所があった。

オッ

ダメ

という訳で白鳥はいたもののドイツのトリは撮れませんでした。自然公園としては気持ちがよさそうなので、やることなかったら遊びに行くのもいいのでは。芝生部分はそこら中にペレットが落ちています。 あと近くにあるBMW博物館は車に興味がなくても面白かったです。オススメ。

おまけ

イタリアのトリです。ローマのなんだっけ、フォロ・ロマーノとか言う所で見ました。

イタリアのあずにゃんです。イタリアのオタクショップに行ったらハンガリー人のオタクに日本語で話しかけられてやばかった。

明日はあっきぃさんです。

さんまの梅煮

食べたくなったのでさんまの梅煮を作りました。

あずにゃん、さんまの梅煮、炊き込みご飯、大根と豚バラの味噌炒めです。

さんまの梅煮:こんぶつゆ25cc、日本酒25cc、梅干し(塩分22%)2個、水適量(煮込むと蒸発するので量はどうでもよい)を煮立ててから弱火にして、頭と内臓を取ったさんまの筒切りを投入して煮込みます。落し蓋がないと味がうまく行き渡らないと思います。 今回は1時間半くらい煮込んだところ、骨まで柔らかくなりました。微妙に生ぐさいので生姜を入れたほうがいいかも知れません。前回はネギを入れたんですが、塩味をめっちゃ吸ってしまいネギとして食べるのは厳しい感じになりました。というかそもそもこのレシピだと塩が強いので、こんぶつゆの代わりに塩分のないダシを使うのが良いかもしれません。今度やります。

炊き込みご飯:米2合にこんぶつゆ25cc、日本酒25cc、戻したひじき(乾燥時5gくらい)、きざみ揚げ、まいたけを入れて炊きます。結論から言うと味が薄いので、醤油を倍量くらい入れてよいと思います。まいたけは食べるとあまり味がしなくなっているのでご飯にダシが出ているんだと思いますが、よく分からないです。でも動物性の具を使わずにそれなりの味になりそうな気配は見せているので、ベジタリアンの人になんか振る舞うとき便利かもしれません。

大根と豚バラの味噌炒め:大根(いちょう切りで24枚くらい)、ネギ1本、豚バラ100gくらいをこの順で炒め、味噌小さじ2くらいとチューブ生姜適量を日本酒と水で溶いて和えます。豚バラをフライパンの上で放置しておくと自分の脂で勝手に揚げ焼きになり、香ばしくなるということを発見しました。大根は火の通りが遅く、固いと悲しい気持ちになるので忍耐が必要です。

あずにゃんfigmaです。Amazonの履歴によれば6年前に買ったらしいです。当時はけいおんのアニメ2期が放映されていましたが、今もNHKBSで放映されていることが知られています。

あずにゃんとEscapeキー

新しいMacBookでは、キーボードからEscapeキーがなくなるらしい。

最初にそう聞いたとき、僕はただ、ふぅんと思っただけだった。Escapeキーなんてそんなに使うものでもないし、消えても困るものではないだろう。

「あ、また何か変なこと考えてましたね」

しかし、現実は時として予想外の景色を見せるものだ。今、僕の手元にある新しいMacBookは、女の子がEscapeキーの働きをしている。


「私がそのMacBookのEscapeキーです。よろしくお願いします!」

僕がMacBookを買ったとき、彼女は突然そのように挨拶してきた。腰まで届くかというような黒い髪を頭の両横で結んで垂らしている、小柄な女の子だ。なんだこいつ、と思う間もなく彼女が説明を始めたところによると、Escapeキーがキーボードからなくなる代わりに、付属の女の子がEscapeキーとして働くのだという。そんなことあり得ないだろうと思って彼女を試したのだが、僕が彼女に合図を送ると、MacBookの画面は本当にEscapeキーが押されたかのような振る舞いを見せる。

「分かっていただけましたか?」
「ああ、分かったよ。Escapeキーを押したい時には君に言えばいいんだね?」
「はい、そうです!」

元気よく応える彼女。

「でも、名前がないとちょっと声をかけにくいな。君はなんて言うの?」
「うーん、Escapeキーなのでこれと言って特別な名前はないんですが……。製造コードは『ナカノアズサ』となってます」
「じゃあ『あずにゃん』でいいかな。アズサの『アズ』を、あだ名っぽく可愛くした感じで」
あずにゃん……あずにゃん。いい響きですね。じゃあ改めまして、よろしくお願いします!」

こうして、僕と「あずにゃん」の生活が始まった。


女の子がEscapeキーであると言っても、そもそもEscapeキーはそんなに使うものでもないので、必然的に彼女の仕事は少なくなる。一番よくある出番はゲームを終了するときで、僕はタイトルから終了の項目を選んで終了するのでもいいと思っているのだが、ゲームで一通り遊んでいざ終わろうとすると、それまで横で画面を眺めながら、へぇ、とか、おぉ、とか声を上げていたあずにゃんが、期待に満ちた目で僕を見つめてくる。Escapeじゃなくても終了できるんだけどな、と思いつつも仕方ないかという気持ちでEscapeキーの入力をお願いすると、あずにゃんは待ってましたとばかりにEscapeのキーコードを発行して、ゲームを終了するのである。一瞬だけ画面が暗転してデスクトップに戻った後、あずにゃんはこちらを向いて、どうだ、やってやったぞとばかりに自慢気な笑顔を見せてくるので、まあ、こんな可愛い顔が見られるのであれば次もお願いしようかな、などと僕は考えてしまうのだった。

ゲーム以外でEscapeキーが必要になる場面は、日本語入力の変換を間違えた時が一番多い。変換の文節区切りがうまく認識されなかったり、開こうと思った漢字まで変換されてしまった時などはEscapeキーの出番となるのだが、しばらく一緒に過ごしているとあずにゃんの方も僕のそういった癖を飲み込んでくるようで、あ、またですねと言いたげな顔で促してくる。心の中を見透かされているような気恥ずかしさを覚えつつ、うん、間違えてしまったからEscapeキーお願い、と頼むと、してやったりという表情を隠そうともせず、誇らしげに変換をひらがなまで巻き戻してくれる。そんなあずにゃんを見ていると、なんだか心強い味方ができたような気がして、僕の心が温まるような感覚を覚える。


ある日、あずにゃんとEscapeキーを巡ってケンカをした。発端は僕がVimを使い始めたことで、最初の不慣れなころは何か操作をするたびにEscapeを押してNormalモードまで戻る羽目になり、いちいちあずにゃんに頼んでいた。突然仕事が増えたあずにゃんは嬉しそうで、僕が「あっ」という声を上げるたびに、Escapeを発行したくてたまらないらしく、猫じゃらしを前にした猫のように、うずうずした様子を見せていた。

しかし、僕がVimの操作に習熟していくにつれて、Escapeを使う頻度は減ってきて、代わりに<C-[>を使うことが多くなってきた。別に大きな理由はないのだが、やはりホームポジションから手を動かさずに入力できるし、自分のペースで打てるため、こちらの方が使いやすい。次第に、あずにゃんにEscapeキーを頼む機会も減っていき、あずにゃんはなんだか寂しそうな表情を見せるようになった。

「<C-[>はいいですね、いつも使ってもらえて。私もEscapeキーなんかじゃなくて、Ctrlになればよかったのかな……」

あずにゃんが時折こう呟くのを聞くと少し胸が痛んだが、<C-[>のほうがなんとなくしっくり手に馴染んでしまったこともあり、段々とEscapeキーからは疎遠になっていった。

そしてある日。僕が久しぶりにゲームをしようとすると、なぜか起動しなくなっていた。そんなはずはない、と思って何回か繰り返すと、どうやら起動しないのではなく、起動した瞬間に終了させれられているようだということに気がついた。もしかして、と思ってあずにゃんを見ると、顔をクッションに半分うずめて、怒っているような、泣いているような、どっちつかずの表情を少し見せた後、そっぽを向いてしまった。

あずにゃん、怒ってる?」
「別に、怒ってないです」
「でも、ゲーム……」
「さあ、知らないです。どうせVimの操作を間違えて、設定ファイルを壊したんじゃないんですか」

取り付く島もない。僕はゲームを諦めて、Twitterを眺めることにした。独創的なVimプラグインを書くことで有名な人が、今日もVimネタのツイートを繰り出している。

Vimはいいですよね、こんなに好きでいてくれる人がいて。私の出番なんてないんです」
「そんなこと……」
「私だって、最初の頃は仕事が増えて、大変だけど楽しかったです。Escapeキーなんて全然使われないと思っていたのに、突然主役みたいになって。ああ、Vimっていいな、Vimがあれば、Escapeキーでもこんなに使ってもらえて、話しかけてもらえるんだって、そう思いました」
「でも……」
「でも、モードを切り替えるのは<C-[>の方がやりやすいんですよね。Vimも最初からそのキーバインドがあるし、元からそういう物だったんですよ。勝手に調子に乗って、喜んでた私がバカみたいじゃないですか。結局Vimに踊らされてるだけで、そのVimにもこんなに熱心なファンの人がいる」
あずにゃん……」
「もう、ほっといてください。Escapeキーなんていらないんでしょう!」

僕は、それ以上何を言えばいいのか分からなかった。MacBookを閉じた後も、頭の中ではあずにゃんの言葉がぐるぐると渦を巻いていた。

ISUCON6予選で負けたので反省します

ICFPCが楽しかったのでまたチーム戦しようという話になり、かねてから興味のあったISUCON6にチーム「イーグルジャンプ」として、id:pepsin_amylaseとmkutとともに参加しました。Python実装を選び、8時間かけて12830点しか取れず、社会的には完全に人権を喪失しました。以下、反省文です。

やったこと

  • Opsフローの策定 (osa_k)
    • 基本的にはpull-reqベースで開発を進める。masterは常にデプロイ可能な状態に保っておき、featureをマージするときにはmasterからfast-forwardできることを要求する。
    • 設定ファイル類はすべてリポジトリに入れておき、通常のデプロイ操作だけでnginxやMySQLの設定まで変更できるようにする。
    • デプロイ時に変な操作をして環境破壊することを防ぐため、Rundeckジョブを叩いてデプロイする。
  • 本番サーバの他にテスト用サーバを立てる (osa_k)
  • 秘伝のタレnginx.confとmy.cnfをサーバに置くが、たまたまアプリ実装のバグでアプリの起動に失敗し、原因を切り分けるため設定ファイルをロールバックしたまま忘れる (osa_k)
  • RundeckとSlackのインテグレーションを設定する (osa_k)
  • 静的ファイルをnginxで配信する (osa_k)
  • isutarをisudaに併合する (mkut)
  • ついでにスター処理をRedis化する (pepsin_amylase)
  • ログイン処理とキーワード一覧の取得をRedis化する (pepsin_amylase)
  • リンクのsha1をキャッシュする (pepsin_amylase)
  • htmlifyの結果をキャッシュする(別プロセスでひたすら最新30件のキャッシュを作りRedisに突っ込む) (osa_k)

やってよかったこと

情報共有の時間を取る

全員の作業のキリがよくなりそうなタイミングで小休止を挟み、現在自分が何をやっているか、何が分かったかという情報を共有する時間を設けることで、全員が全体の状況を把握できるようになるとともに、改善案を取捨選択して重要そうなものに注力することが可能になりました。相談せずにずっとコードを書いているとだんだんノッてきて書きたいものは増えていくのですが、すべて実装する時間はないのでアイデアの切り捨てが必要になります。強制的に休憩を取ってミーティングを行うことで、全員でアイデアの共有と吟味ができるため、大きな手戻りは防ぐことができました。1人でもそのへんは同じだと思いますが、チーム戦だと誰か1人が言い出せば良いので、アイデア吟味の時間を取り忘れる可能性が下がるのが良い点です。

開発フローを定める

今回はmasterを常にデプロイ可能な状態に保ち、各自がfeatureブランチをmasterから作ってfast-forwardな状態を保つことで全体の信頼性を確保する、いわゆるGithub FlowContinuous Deliveryと呼ばれている手法を使いました。ISUCONのようにあるタイミングで必ず動くものを提出しないといけないというコンテストでは、常にmasterがデプロイ可能という状態は安心感がありました。

ただし、featureの粒度を小さく保つのが難しく、あるfeatureで関係ない箇所のバグに気づいてfixを混ぜてしまい、他のfeatureでもそのfixが必要になったため、masterからではなくfeatureからブランチを生やすというケースが発生してしまいました。こうなるとそのfixを含んだfeatureがrejectされたときに大変面倒な状況になるため、運用には専用のgitコマンド群を用意するなど、ある程度のDevOps作業が必要になると感じました。

やるべきでなかったこと

Rundeckを小規模オペレーションに使う

これが(自分が担当した中では)一番の敗因でした。コンテスト中は、アプリのデプロイ手順はテスト環境でも本番環境でも変わらないという理由により、ひとつのRundeckジョブにテスト用と本番の両方のノードを登録して、ジョブを叩く人がデプロイ先を選ぶ形にしていました。しかし、そもそもRundeckはChefやCapistranoのように、複数のサーバで同じコマンドを同時に叩くというオペレーションが主眼に置かれており、本番とテストのデプロイ操作をひとつのRundeckジョブで共有しようというのは、かなり踏み込んだ自動化をしないと無謀です。実際、テスト用サーバで作業している最中に間違ってデプロイを叩かれ、作業中のデータが消えるという事件がありました。また、ノードのマシン名やログイン情報などは手書きでXMLを編集しないといけなかったり、ジョブ定義のUIが煩雑でTry & Errorを前提とした改修に向かなかったりと、設定作業が割と煩雑なため、どうしても使いたいのであれば、プロジェクトの生成から全部スクリプトで自動化するべきでした。練習中はRundeckジョブを叩いてくれるhubotを運用していたのですが、Rundeckを通してデプロイしていると開発のテンポが悪くなることと、そもそも本番で設定がうまく行かずに動いてくれなかったため、手動でRundeckを叩く運用にしたのも良くなかったです。

とは言えGUIでサーバを触れるようにするという方針は、実行ログや処理内容が一箇所に集約できて管理が楽になるという点で、間違っていなかったと思います。後述のように本番環境と開発環境の分離がうまくできず、Rundeckで管理する対象の大きさが不必要に膨れ上がってしまった点が問題なので、これは本当に集約管理しないといけないか?という切り分けをリソースごとにじっくり考えるべきだったと言えます。

環境設定をしていた時間のほとんどはRundeckの設定を調整していた時間になっていたので、この規模の用途でRundeckを使ったのは完全にオーバーキルであったと思います。使うにしても、設定を可能な限り自動化してやるべきでした。

開発用サーバをケチる

練習の段階では、ローカルで環境構築する手間を省くために個人ごとに作業用VMを立てようという話をしていたのですが、AzureのFree tierプランでは1リージョンに4コアまでしかマシンリソースを置けないという制約があり、面倒だったので開発用兼テスト用サーバの1台しか用意しませんでした。また、前述のようにRundeckの設定が煩雑だったため、ノード数を増やすと作業量が増えてしまうという問題もありました。結果として、3人チームなのに2人しかサーバ環境が使えず、1人はローカルで環境構築する必要があるという二度手間になってしまいました。

急な方針転換はオペレーションが混乱するため、可能な限り避けるべきです。設定ファイルのチューニング忘れも完全にこの辺の混乱が尾を引いてしまった形です。また、アプリの開発環境整備は、処理系のバージョンを揃えたり、DBの導入をする必要があったりと結構面倒な作業を(特にMacを使っていたりすると)含むため、なるべくなら自動化されたものをそのまま使う方が良いです。

開発環境とテスト・本番環境の分離をはっきりしない

開発用サーバとして用意したサーバは、実質テスト用サーバを兼ねており、作ったブランチを試したいときにはアプリをデプロイし、ベンチマークに投げるという運用がなされていました。しかし、当初はデプロイ処理の大部分がRundeckジョブとして記述されており、ちょっとしたテストをするにもブランチをpushしてどこかにデプロイするという作業が必要で、全体で1〜2分ほどかかっていたため、かなりまどろっこしい運用になってしまっていました。

後半ではデプロイ作業の大部分をdeploy.shとしてまとめ、Rundeckジョブではこのシェルスクリプトを叩くだけになりました。こうすることで、開発用サーバ上でも開発者がdeploy.shを叩くだけでデプロイできるようになったのですが、開発環境はコードの編集からデプロイまでの素早いイテレーションが求められ、テスト環境では可能な限り本番に近づけた運用が求められるということに気づかず、ごちゃ混ぜの運用をしていたのは大きなミスでした。そもそも今回は短時間コンテストな上、間違えて環境を破壊しても復帰が容易だったので、テスト環境という概念自体が不要だったと思います。

プロファイルを取らない

ボトルネックの特定は主にnginxのアクセスログの解析と目視のコード確認で行っており、トップページのレンダリングにかなり時間がかかっていることは比較的早い段階で分かっていたものの、キーワードやログイン情報のキャッシュなど、本質的でない箇所の高速化に時間を割いてしまい、htmlifyのキャッシュに取り掛かったのは終了2時間前でした。ちゃんとした経験か洞察力があれば、巨大な正規表現ボトルネックであると看過できたはずなのですが、そうでない以上は闇雲に最適化を進めるのではなく、行レベルのプロファイリングなどで実行時間を測定し、定量的にボトルネックを探すべきでした。

このあたりはサーバ設定が遅れたことでオペレーションが混乱し、データが欲しいときにすぐ得られる状況でなかったため、nginxのログの確認すらスムーズに進められなかったことも大きな原因になっていると思います。環境構築の遅れはとにかく致命的です。

まとめ

せっかくのチーム戦だし、普段から興味のあったオペレーション役をやりたいと思っていたのですが、過度に複雑なオペレーションを組んでしまって爆死しました。開発経験なさすぎですね……。ツールの使い方や最適な開発フローなど、自分1人の開発でも勉強できた点は多かったと思うので、個人プロジェクトでいろいろ試して来年のISUCONまでに経験値を積みます。

ICFPC2016 Team 天羽々斬

ICFPC2016にTeam 天羽々斬として参加しました。チーム名は、直前に観に行ったシン・ゴジラ(名作なのでまだ観てない人はこんなブログ読んでないで今すぐチケットを予約しましょう)で、ゴジラ討伐部隊の1つが「天羽々斬」と呼ばれており、それを聞いた瞬間にシンフォギアだ!!!!となって勢いでチームSlackのドメインをamenohabakiriにしたところ、そのまま流れでチーム名となってしまいました。

f:id:osa_k:20160811023921p:plain

チームメンバーは弊社同期の8人です。8人!多いですね。確か2012年にBoulder Dashやったときも東工大の西8号館にそれくらい集まってた気がしますが、人数が多いとそれはそれでうまく回すのが大変ですね。

ICFPCはチームを組むと作業の分担ができて楽しいのですが、今回も例に漏れず、アルゴリズム班とインフラ班になんとなく分かれるような感じで計画していました。最終的にはインフラ1:アルゴリズム4:手動(!)3みたいな配分になっていました。僕はアルゴリズム力が(比較的)弱い&サーバ周りの作業が好きという理由でインフラをやりました。自分以外の人間が集中的に使うサービスの管理って実際に手を動かさないと勉強できないんだけど、意外とそういう機会ってないんですよね……。

ということで、自分が何をやったのかを中心に、3日間のチームの動きを書いていきます。ちなみに同じチームの id:pepsin-amylase は仕事が速く既に記事を書いているので、合わせて読むと面白いかもしれません。

pepsin-amylase.hatenablog.com

あと提出したコード群。

github.com


0日目

せっかくのチーム戦だし、普段できないサーバの使い方をしてみたいと思ったので、imosさんの記事を参考にして環境を作ることにした。チームメンバーと相談して、結局以下のような方針に決まった。

  • サーバはEC2のr3.8xlargeをスポットインスタンスで使う。1時間あたり$0.4程度が相場のようなので、72時間取っても$30程度。十分安い。
  • ファイルは基本的にサーバ上に置き、各自sshfsでマウントして管理する。imosさんの記事にあるDropBoxはメリットがよく分からないので見送り。
  • 上記の性質上Gitは使えない or 使いにくいため、バージョン管理はしない。定期的にバックアップは取る。 -- マイクロサービス群も特別なデプロイ作業はせず、共有ディレクトリ上から直接実行する。
  • コンパイルと実行は環境を揃えるため、サーバ上で行う(これはあまり徹底されていなかった)。

チーム名が天羽々斬なので、サーバのドメインtsubasa.osak.jpにした。

1日目

開始と同時に公式ブログに飛んで問題文を読む。折り紙を折った後の形が与えられるので、そういう形になるような展開図を構成する問題らしい。えっ、幾何問題なの……。無限精度の有理数ライブラリが必要とされているらしく、ICPCにありがちな誤差ゲーは避けられるものの、多倍長が組み込みの言語かそういうライブラリを持ってないと無理だよなぁという空気になる。GMPを使うかHaskellを使うかみたいな話が出たものの、kawateaが有理数ライブラリを持っていてなんとかなるかもと言っていたので、とりあえず任せることになる。

問題背景のセクションには、大ブッダ像に祝福されたオリガミを供えて庇護を得るだの、古事記にも数々のオリガミが登場するだの、色々とパワーワードが散りばめられている。電通大が主催だし、やっぱりニンジャスレイヤー好きなんだろうなぁ。

1人で黙々とサーバのセットアップをしている間に、他の人々は折り紙の性質について議論をしたり、本物の折り紙を買いに行ったりしていた。開始から2時間くらい経ったところで各人のやっていることをすり合わせるフェーズに入り、いくつかの事実と、いくつかの方針案が出た。

  • 目標となる形がすべて有理数の座標だからと言って、展開図にしたときも有理数の範囲で表現できる座標に収まるの?→折り紙の周は有理数長さでしか分割されないので、必ず有理数の座標が取れる。
  • スコア効率を良くする方法は?→問題サイズによって決まる基準点があり、完全に一致する折り紙を折れたN人でそのうちのN/(N+1)を山分けし、残りの1/(N+1)を完全一致でない人たちが一致度で比例配分する、という採点方式なので、完全一致解以外はほとんど意味がない。近似ソルバ作ってないで厳密解ソルバに全力を注ぐべき。
  • そもそも展開図から折ったときの形って一意に定まるの?→どうやら定まるらしい。今回は重なりの順番は無視していい(物理的に不可能な重なり方であってすらよい)ので、一般の折り紙よりは楽。
  • 凸包を作ってその形に合わせて折っていけばよいのでは?
  • DPっぽく外周になり得る辺をたどって、長さ1の辺を4本復元できたらなんとかなるのでは?

このうちDP解は展開図の復元が無理そうだったので、凸包解をkawateaが書き、その他の人は折り紙の折り方を研究するなり、必要そうなツールを書くなりすることになった。自分はREST APIを使った問題クローラを書いていた。今回は運営側からREST APIが提供されていて、すべてのアクセスはAPI越しでやれ、むしろ人間用のUIをクロールするな、あと1時間に1回しか更新しないから無駄にクロールしてきて負荷かけんな、という今までのICFPCの反省をふんだんに盛り込んだ制約がかかっていて、運営めっちゃ頑張ってるなぁと思った。

クローラを書き終えるころ、yuustiがビジュアライザ書いたんだけど、これWeb UIで見せることできますかと聞いてくる。どんなのを作ったか聞くと、問題ファイルを入力にして、imosさんのJSビジュアライザの関数呼び出し列を吐くようなものだったので、まあ外部プロセス呼び出しでくっつければいいかと思ってSinatraでフロントエンドを書く(この時は多倍長有理数ライブラリをつかっておらず、doubleを超えるような入力が来て破綻したので後でRubyにポートした)。ついでにUserJSを書いて、公式の問題ページからビジュアライザに飛べるようにする。今思えばこの判断は微妙で、とっとと静的な画像を生成して一覧表示しておくべきだった。あと、無駄に1回生成したJSをキャッシュするような機構にもなってたが、プロセスの呼び出しなんてそんな負荷でもないので、ビジュアライザがアップデートされて情報が増えることを考えると、おとなしく毎回生成するべきだった気がする(実際後で何回も問題になった)。

必要最低限のインフラはできたので、次は各人のプログラムをすべての問題に対して実行した結果を残しておき、性能比較ができるようなシステムを作ることにする。とりあえず逆羅刹と名付ける。実行のスケジューリングとかをRubyで書くのは流石にだるかったし、コケたときのログを残すのも面倒なので、JenkinsのParametarized Buildで実行対象のバイナリを指定したら勝手に走らせてくれるようにする、という方針にした。Jenkinsはなんかゴツいイメージなのでカ・ディンギルと名付けた(適当)。

ビジュアライザで古事記(運営が用意した問題は、すべて古事記に記されているらしい)の問題を見て、10番台に1回だけ折った問題が多いことに気付いたkeitakが、1回折り専用のソルバを書いていて、ちょうど逆羅刹の完成と同じくらいだったので試してみる。

f:id:osa_k:20160811143306p:plain f:id:osa_k:20160811143310p:plain

(Problem 11と12)

何回か失敗したものの、ちゃんと走った。実行結果一覧を眺めていたら、これ一括サブミットできるといいよねとkawateaが言い出したので、ボタンを押した瞬間に全サブミットのループが走り、終わるまで返ってこないという闇のエンドポイントを生やす。このあたりで単純なファイルだけではデータの管理が難しくなってきたので、MongoDBを導入する。その後kawateaの凸ソルバも走らせるようにする。ビルドまでJenkins側でやるようにしたら、jenkinsユーザが644の他人のディレクトリに書き込みできなくてちょっと面倒くさいことになったので、パーミッションを777にすることで解決した(sysadの屑)。

yuustiがまたなんか言ってくる。どうやら今度は解答のビジュアライザを書いたらしい。問題ビジュアライザと同じ仕組みが使いまわせるのでそのようにする。今ひとつ役に立つのか分からなかったが、手動でひたすら折り紙を折っては展開し、折り目を見て手作業で座標を計算していた勢(主にhyonhinaとmkut)には便利だったっぽい。

mkutは「ねじり」を理解したとか言って、方眼紙の上でひたすらねじりの入った折り紙の座標計算をしていた。後で聞いたところ、ねじりは大体90度回転させると一致するやつだから問題は普通の折り紙の4倍簡単で、辺をじっと見てうまく繋がりそうなパスを見つければいいだけだみたいな事を言っていた。よくわからない。

f:id:osa_k:20160811143738p:plain f:id:osa_k:20160811143820p:plain

(Problem 19と98)

この辺で手作業で解かれた問題が溜まってきて、とはいえ運営の用意したフォームで提出するとログが残らなくて不便なので、何かしらログが残る手段で提出したいという自然な需要が生えた。インフラしかやっていなくてもその必要性は十分に感じていたので、即座に提出用のコマンドと、サブミットキューを消化していくデーモン(蒼ノ一閃)を作る。このデーモンが止まると点数が取れなくてやばいので、 id:pepsin-amylase が作ったコマンド(レイシス)でチームSlackに死亡通知を流すようにしたところ、文章が面白かったらしくなんかウケた。

f:id:osa_k:20160811041416p:plain

開始から24時間までがLightning Roundで、この時点での1位は別に表彰されるので結構頑張っていたのだが、3位~4位をうろうろしている上、凸包解法ではそれ以上伸ばせそうになく、半ば諦めていた。するとkawateaが突然、手で座標入力したら折れる気がすると言い出してツールを書き、ビジュアライザで表示した頂点の座標を見ながら簡単な問題を手動で解き始めた。やばい。しかし奮闘むなしく、4位(だっけ?)に留まってしまう。

24時間を過ぎたところからは各チームの問題提出が始まる。解答ファイルをアップロードすると運営側で自動的に折られて問題が作られるようになっており、(5000-解答ファイルのバイト数)/(厳密解で解いた人数+1)の点が入る。解いた側の基準点は解答ファイルのバイト数になるため、2500バイト以内であれば提出するだけ得となる。とりあえず古事記にあった、Fの形をした折り紙をhyonhinaが手作業で解いており(やばい)、他のチームも解いていないようなので、こいつをmkutのGideon(解答ファイルを食わせてコマンドを指定すると、平行移動・回転・任意の直線で折り返しした解答ファイルを吐くやつ)でちょっと回転させたものを提出することにして、家に帰って寝た(ここで朝10時くらい)。

f:id:osa_k:20160811144015p:plain

(Problem32 「F」)

2日目

16時くらいに目覚めて会場に向かう。どうやらcronで仕掛けておいたクローラが寝ている間にうまく動いておらず、問題の更新が滞っていたらしい。cronは難しすぎてよく分からないので、クローラもJenkinsで回すことにする。最初からそうするべきだった……。

kawateaが完全に手折り職人と化してしまったので、折り紙ヘルパーを作ることになる。折り途中の折り紙から折りたい辺と面を指定するとその通りに折れるというもので、例によってバックエンドで動くステートレスなC++プログラムをSinatraが叩いてユーザに送り返すというシステムになった。

https://raw.githubusercontent.com/osak/ICFPC2016/master/images/gekirin.png

これを得たkawateaがものすごい勢いで折り紙を折り始め、簡単だけど手入力は厳しいような問題を次々と人力で解いていってしまった。AIなんていらんかったんや……。手動で解いていたhyonhinaとcarbon_twelveも謎の才能に目覚め始め、特にhyonhinaが、未知の折り紙を勘でどんどん実際に折っていっては展開して座標計算してSubmitという行為を繰り返しており、まさに若者の人間離れだと思った。

f:id:osa_k:20160811145728p:plain

(Problem18 「パカ」(yuusti命名) 現実には内側を開いて潰すように折る操作(潰し折り)が必要になるが、今回の問題では過程は無視して展開図さえ作れれば受理されるため、直線で折り返すだけで作れる(途中で面が切れてもう一度くっつく))

その間に id:pepsin-amylaseグレイスを作って平行移動、回転、鏡像反転で同じ問題になるものを使いまわせるようにしたり、yuustiが天空の夜明けを作って非凸の折り紙を1回だけどこかで展開し、凸にした上でkawateaの凸ソルバに食わせ、最後にmkutのGideonで元の線で折るというパイプラインによる解法を開発し、100問くらい解いた(らしい)。Gideonは元々、古事記の難しい問題をもう1回折ればめちゃくちゃ難しくなるだろうし、回転させると適当な合同判定ルーチンしか持ってないチームには解かれないだろう、という発想の出題用ツールだったのだが、問題を解くためのパイプラインになったのは面白い。この方針でもっと解けばよかった気がする。

そんなことをしていたら、なんとUnagiを抜いて1位になった。魔剤?

Unagiは潜伏しているのかもしれないけど、他のチームも似たようなもんだし、こんな探索しづらそうな問題で大きく差は付かないだろうと思い、現状維持の方針を取ることにする。結果的にはこれが完全に分水嶺だった。とりあえず、グレイスが合同かつ未解決な問題をクラスタリングして提示してくれるようになったので、逆羅刹にクラスタを見るページを作り、手動勢は大きいクラスタから解いてもらうようにする。あと、このクラスタが解けたら何点取れるかも計算したのだが、なんか出力がおかしいのでとりあえず放置する。

このへんから、DAOを書かずに色々なアプリケーションから勝手気ままにMongoを叩いていた影響で、あちこちのフィールドで型が想定とズレて死に、火消しに奔走する。朝になって集中力が保たなくなってきたので、帰って就寝。たしか13時くらい。

3日目

寝て起きたらまたクローラが止まっていたらしい。どうやら前日に帰る直前に入れた変更が良くなかったらしい。ずっと起きてると無自覚でも思考能力がガタ落ちしているので、変なことしちゃだめですね……。そもそもなんでテストしなかったのか。

点数計算のバグがわかる。Problem IDから自分のベストスコアへのHashだと思ってたオブジェクトが実はArrayだったため、変な数値を計算に使っていた。静的に型の付かない言語はこれだから……(責任転嫁する屑)

夕食に寿司を食べていたら、スコアボードも寿司になっていた。一瞬もう凍結されたのかと思った。

Unagiとギリギリ競っているので、Unagiが解けなさそうな問題を出して差を広げようという話になる。とりあえずUnagiがFの折り紙を解けないとは思えないので、もっと難しい問題を作りたい。Unagiが提出している問題を見ると、そもそも折っていくにしろ開いていくにしろダントツで難しい。そこで、Unagiは古事記くらいは解けるにしても、彼らが自分自身で出している問題は解けないんじゃないかという仮説を立て、同じような問題を作ることにする。が、そもそもUnagiの問題が手動でも解けない。仕方がないので、Unagiの問題を解こうとしながら天ノ逆鱗で試行錯誤している最中にできた、Unagiの問題もどきを提出すればいいのではないかとkeitakが主張し始めたので、そういうのを出してみることにする。Unagiの似非なので穴子と命名される。

f:id:osa_k:20160811145533p:plain

(Problem3937 「穴子」)

穴子を出して様子見していたところで2時になる。と、Unagiが10万点くらいスコアを上げて一気に引き離された。えっまじで。潜伏していたのはいいにしても、戦闘力が想定と全然違うっぽい。やばい。 このままだと2位も危うそうなので、特殊ソルバでもいいからもっと頑張って作ろうということになる。自分が起きてくる前に問題一覧をサーベイした人たちによると、yowaさんの問題がかなり特殊な形式になっているらしく、簡単(簡単とは言っていない)な貪欲で解けるらしい。id:pepsin-amylase が専用のソルバを書き始める。

f:id:osa_k:20160811151742p:plain

(Problem1726 ひとつ角を固定し、その角を通るような直線で扇状に折りたたんだ図形になっている。対角線で折られることがないようなので対角の特定が容易にできて、あとはその角から生えている2辺を特定すればよい。)

スコアボード凍結直前の3時。Unagiが70万点くらい、天羽々斬が20万点くらいと、もう話にならなそうな差が付いている。既存の問題を見てみても、1チームだけが厳密解を提出しているものがザクザク出てくる……。これはどう考えてもUnagiが厳密解ソルバを持っているということなので、今から勝つのは無理っぽいし2位維持をがんばろうということになる。つらい。

インフラはいい加減やることがなくなってきたので、少しmkutと議論をする。与えられたシルエットから面どうしの繋がりを維持しつつ正方形にパッキングしていく問題を考えると、探索として解けるかもしれないらしい。ぱっと見はめちゃくちゃ計算量が大きそうだが、確かに色々な枝刈りが考えられるので、枝刈りのプロがいれば実装できるかもしれない。しかし6時間でバグなく高速なソルバにできる自信はなかったので見送られてしまった。

そうこうしている間にyowaソルバが完成する。数回のデバッグと実行を経た後、yowaさんの問題をすべて解くことに成功する。やったー。

また、Skobochkaというチームが、細長くたたんだ折り紙をS字に曲げたものを大量に出しているのを発見したので、yuustiがソルバを書き始める。正確には最初はめんどくさがっていたのだが、Gideonで折る面と折らない面を区別できるようになったら実装してやると言ってmkutを煽っていたところ、mkutが実際にGideonをアップグレードしてしまったため、ソルバを書く羽目になっていた。かわいそう。 残念なことに、このソルバは終了までに間に合わなかった。

f:id:osa_k:20160811153218p:plain

(Problem3961 「S」)

kawateaとhyonhina、keitak、carbon_twelveがひたすら手動で問題を説き続け、時間が来て試合終了。結局手動解法に偏りすぎてしまったのが少し残念。

反省

成り行き上仕方なかったとはいうものの、1人でインフラを全部回すのは厳しかった。システムを作るところまではまあいいけど、実装に使う技術をちゃんと考えておかないと、だんだん体力が削れていく中でデバッグと保守を行うことになり、少しずつシステムが壊れていく。

チームの戦略としては、ソルバにモジュール性を持たせて、雑ソルバでもいいから量産するべきだった。というかそういう風にしたかったのだが、インフラ開発が忙しすぎて自分ではソルバが書けなかったので、他人を説得してそういう方針に引きずり込むのもちょっと難しいという状況になっていた。チームで反省会をしたときにも話題に出たが、数時間に1回くらい完全に作業を中断し、みんなで状況を共有して次の一手を決めるような会議が必要だと思う(というのをICPCに参加していたときからずっと言っているができておらず、成長していない……)

とはいえ、良いサーバを使ってマイクロサービスを立てまくるのは、サービスの本当に最初期の設計を考えるという点でかなり得るものがあった。人数が少なければ指数的に規模が膨らむことはないので、精神的に余裕があれば割といつでもリファクタリングはできる。ファイルベースの管理からMongoに切り替えるとかね。ただし複雑度は指数的に膨らんでいくので、ある程度長期に使われそうなコードならバグりにくい設計を考えておくべきだと思った。あと既存かつ実績のあるプロダクト(Jenkinsとか)はハマリどころが少ないので神。

また、一般論としてサーバサイドアプリとしては異常な構成であっても、割りきってコードの簡略化やモジュール化を進めるのはかなり有効だった。たとえば、REST APIを叩くときにはRubyのNet::HTTPではなく外部コマンド実行でcurlを叩くようにしたり(楽だから)、stdin / stdoutを介した通信でバックエンドのC++プログラムに面倒な幾何をすべて任せたりすることで、管理しないといけない領域を効果的に狭めることができた。

Gitを使わずにsshfsでみんなのファイルを共有して、かつ即デプロイするような環境を作ったのは一長一短だった。他人の成果物へ即座にアクセスできるのはかなり使いやすい。しかし、誰かが使っているコマンドやサービスを改修しようとすると必ず干渉してしまうという欠点がある。特にSinatraのviewは変更が即座に反映されるので、実装途中の機能がユーザに見えてしまうことがよくあって一瞬ではあるものの混乱を招き、精神的に負担が大きかった。

総括

3日間ずっとめまぐるしくコードを書いていたのは楽しかったです。自分がインフラを作っていると、他の人がインフラを使って成果を出してくれるという体験をこの密度でできるのは、なかなか得難い体験ですね。折り紙という題材を最初に見た時はどうなることかと思いましたが、最初の印象以上に問題もジャッジシステムも完成度が高く、ずっと飽きずに、かつシステム面でフラストレーションが溜まるようなことがなく、遊び続けることができました。

チームの動きとしては、もっと探索して計算機の力を使うようにするべきだった感はあります。なまじ人数が多いというのも考えもので、今回のように人間がある程度能力を発揮できる問題だと、すぐに人海戦術に流れてしまう。4,5人しかいなければ確実に探索の方針を考えていたのではないかと考えると、もっといい戦い方はできたんじゃないかなぁと思います。

来年はうなぎに勝ちたいですね。

Windows10 on VirtualBox

Windowsでないと動かず、かつ使えないと困るプログラムがそれなりにあるので(ScanSnapのドライバとかiTunesとか)、立てて放置してた自宅サーバにWindows10を入れることにしました。まあWindowsにホストは渡したくないのでVirtualBox上です。

一応Headlessでサーバ管理したいみたいな目標があるので、GUIフロントエンドを使わずにVBoxManageでちくちく頑張って仮想マシンを立ち上げることにしました。よく分かんないので適当にぐぐったところ、良さそうなページを見つけたので、真似して以下のようなコマンド群でVMを作りました。

$ VBoxManage createvm --name Windows10 --ostype Winddows10_64 --register --basefolder `pwd`
$ VBoxManage createhd --filename Windows10/Windows10.vdi --size 20000
$ VBoxManage storagectl Windows10 --name "SATA Controller" --add sata --controller IntelAHCI
$ VBoxManage storageattach Windows10 --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium Windows10/Windows10.vdi
$ VBoxManage storagectl Windows10 --name "IDE Controller" --add ide
$ VBoxManage storageattach Windows10 --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium host:/dev/sr0
$ VBoxManage modifyvm Windows10 --ioapic on
$ VBoxManage modifyvm Windows10 --boot1 dvd --boot2 disk --boot3 none --boot4 none
$ VBoxManage modifyvm Windows10 --memory 2048 --vram 64
$ VBoxManage modifyvm Windows10 --nic1 bridged --bridgeadapter1 enp0s25

DVDドライブをIDEで繋いでてちょっとどうなのという気がしなくもないですが、変にアレンジしてハマるのも嫌だったのでそのままにしました。長いものには巻かれろ精神です。この状態でWindows10のインストールディスクを入れて

$ VBoxHeadless -s Windows10

するとVMが立ち上がるので、

$ rdesktop kadingel:3389

とかやってリモートデスクトップで繋ぎます。するとインストーラが出てくる……はずだったんですが、CPUが64bitに対応してないよ的なエラー(0xc000035a)が出てきて進めませんでした。ちゃんとCPUは64bitだし、VT-xも有効化してるのにどういうことなんだ……。

しばらく調べてると、どうやらWindows10は新しいチップセットしかサポートしていなさそうという情報が見つかったので、

$ VBoxManage modifyvm Windows10 --chipset ICH9

すると、今度はうまくインストーラが立ち上がってくれました。ハマるの嫌だから先に色々調べたのに、結局3時間くらいかかってしまった。