As Sloth As Possible

可能な限りナマケモノでありたい

タイトル通り。なんでそんなことになったのかという経緯はこう。

  1. TOKYO JUNGLEにハマる
  2. TOKYO JUNGLE熱が動物熱に発展する
  3. なんと地元の超小規模な動物園が日本でも数少ないブチハイエナのいる動物園であることを知る
  4. ちょくちょく通ううちに、俺はiPhoneのカメラでパシャパシャやっているというのに、最近の幼児はデジタル一眼レフを使いこなしていることを知る
  5. うらやましい
  6. それなりにちゃんとしたカメラ欲しい

…という、子供よりも子供っぽい理屈によりカメラ欲が高まった結果、大人気ない大人の経済力を発揮(夏のボーナス万歳)してカメラ買ってきました。買ったのはSONY α NEX-5N ダブルズームレンズキット。あんま良く分かんないのでどれがいいのーって同僚に聞いたところ、「NEXだったら液晶がパカパカ動いて便利」「鳥とか撮るんならズームレンズは必須」「SONYのEマウントはレンズそんなに種類出てないから沼にはまる危険性が低い」という話だったので。殆ど何の知識も技術も無いのでぶっちゃけどれでも良かったのだけど、初めて買ったミラーレス一眼というのはなかなか素敵な代物だったので大変に満足しています。

その辺のハト

まぁまだこんなの撮って遊んでる段階なのでどやこや言えることは特にないです。動物園通ったりその辺の猫とか撮ったりしてHDDのモフモフ度合いを高めていく所存。

あ、ちょっと気になったのは、NEX-5Nの「ズームレンズキット」「Wレンズキット」に付いてくる"ズームレンズ"は「E 18-55mm F3.5-5.6 OSS」だったのだけど、これ想像してたより「ズームじゃない」感じしたので、「ビルの上のカラスとか、荒川を泳いでるカモとか撮りたい」と思ってた俺にとってはもう少し遠くまで撮れる「E 55-210mm F4.5-6.3 OSS」が付いてくる「Wズームレンズキット」の方にしといて正解だったなーと。逆に普段使う分には「E 18-55mm F3.5-5.6 OSS」の方で別段困ってないし。きっとまぁこういうのを買う人は数字見てちゃんと分かるんだろうけども、参考までに。

ゴールデンウィークを利用して久々にろくでもないものができたので晒しておく。

「ごっこじゃないよ、兄ちゃん!」「才能の無駄遣いじゃなくて、無駄そのものだよ、お兄ちゃん。」

前のと何が違うの?

そもそもの発端についてはプログラミング言語「DT」という記事を、実際の言語仕様についてはREADMEでも読んでもらうとして、何で再実装したのかという話。

3年前に作ったのは「Whitespaceのトークンを置換した処理系をインタプリタとしてRubyで実装」したものなんだけど、何を思ったかコンパイルしてみたいと思い立ってしまい、んでそれ自体はLLVMが良く分かんなかったとか、llvmruby自体がプロジェクトとして大分アレな感じになってしまっていたとか、そういう事情で挫折してたんだけど、時は流れ、なんか見返してみたら普通にLLVMアセンブリ読み書きできるようになってたし、ruby-llvmがちゃんと動くようになってるみたいだし、ということで再実装してみることにした。

そんで作り直す過程で「Whitespaceの置換ってのもなんだか面白くないな」と思ってVMのアーキテクチャと言語仕様を見直すことにした(ぱっと見だと全然分かんないけど、旧DTと新DTの間に互換性は無い)。そのせいで実際にはチューリング完全じゃなくなってると思うし、そもそもちゃんと出来てるのかどうか怪しいものだけど、とりあえず「Hi!」って出力するのとフィボナッチ数を出すのは動いたので良しとする。

当初の目標だった「DTコードをネイティブのバイナリにまでコンパイルして高速に実行」ってのも実現して、DTインタプリタで250msくらいかかるfib.dtをコンパイルすると4msくらいで結果が出るようになった。似たようなRubyのコードだと大体100msくらいだったので素敵に速い。まぁ、コンパイル自体はLLVMとコンパイラに任せているので大して威張れるあれでもないのだけども。そして高速に動いたからなんだという話なのだけども。

ぶっちゃけDTよりLLVMアセンブリの方が遥かに記述力高いし書きやすい。当たり前のように構造体とか関数ポインタとか使えるのな。Cの関数呼べるし。最初Cで書いてたVMを途中でLLVMアセンブリ直接書くようにしたけど、大して変わらない。DTで書くくらいならLLVMアセンブリ書いた方がいいです。LLVMの無駄遣いタグを付けていただきたい。

DTの存在意義については疑問を差し挟む余地もなく皆無だと言っていいけど、その過程でレジスタマシンについて色々調べて、はー、こんぴゅーたーってこうやってうごいてるのかー、と勉強になったのでそれはそれで良かった。よくこんなの考えられるよなぁ。CPUとかコンパイラとか作ってる人達、本当に俺と同じ種類の生き物なのだろうか。凄い。

随分長いことブログ放置してしまったのだけどネタ見付けたので久々の記事。

UnicornはPassengerより遅かった?

なんかTwitterで「アクセス少ないときはPassengerよりUnicornのが速いのに、アクセス多くなってきたらその逆になった」って話をみかけたので、それ単にUnicornのworkerが足りないんじゃないの、と返したのだけど、どういうことかという話を少しまとめる。

まず、Unicornのworkerは1プロセスにつき1度に1リクエストしか処理しない。だから例えば、凄い大雑把な計算だけど、平均50msくらいでレスポンスを返すアプリだとすれば、1workerは20req/secくらいは返せるかなと見積もって、ピーク時に100req/secくらいアクセスがありそうだったらworkerを5個くらい立てとくかな、足んなかったらもうちょっとかな、みたいに考える。実際どんくらいのアクセスなのかは聞いてないので知らないけど、Nginxが10workerいるのにUnicornが2workerだって言ってたから、普通逆じゃない、とつっこんだ。Nginxは逆に1workerで複数リクエスト捌ける(数千くらい同時にアクセスされても耐えられるらしい)のでCPUのコア数と同じかちょっと多いくらいでよくて、Unicornのworkerの方をガンガン立ち上げるべき。

Passengerもそれ自体は同じなんだけど、Passengerの場合負荷上がってきたら空気読んで設定した上限までプロセス数を増やしてくれるので、「アクセス増えても強い」のではなくて「アクセス増えたらいつの間にか仲間呼んで倍の人数で戦ってた」とかそういうことじゃないかと思う。上記逆転が起こるのは「Unicornのworkerが少ない」「リクエスト数が増えても1個1個の処理時間は大して変わらない」「メモリやCPUには余裕がある」って条件のときだと思うので、多分Unicornのworker数を適切な数に調整すれば解決する。

workerの数増やしたらメモリ食うんじゃ、って言う話なら、Unicornはworkerプロセスを立ち上げる前にアプリをpreloadする仕組みがある。アプリ初期化時点で必要なライブラリを全部読み込んであれば、workerプロセスの起動は単なるforkなので、CopyOnWriteで親プロセスとメモリを共有して効率的に使ってくれそうな気配はある。それでも食うっちゃ食うけども、worker一杯立ち上げたらメモリ足んなくなるならPassengerでも同じ話。平常時には節約してくれるけども、裏を返せば急激なアクセス増大に際しては慌ててプロセスを沢山作るコストで負荷上がることもあるし、単体で比較するとUnicornのがまぁ普通に強い。大量アクセスがある程度定期的に来るの分かってるならUnicornで、滅多にないならお手軽なPassengerで、みたいな使い分け。

Rainbows!って何

ここまでの話はあくまで、1個1個のリクエストは大して重くないけど一度に沢山のリクエストが来ちゃったら遅くなった、みたいな場合。例えばさっきと違って、平均で見れば100msくらいなんだけど、20回に1回くらいの割合で1500msくらいかかるリクエストがくることがある、とする。それがたまたま5人同時にリクエストしてしまったら、5workerがそれぞれ1.5秒ずつ拘束されてその間一切レスポンスを返せなくなる。内部ではファイルサーバにデカいファイル書いてるとか、DBに重いクエリ投げてるとか、API叩いて外部からデータ取って来てるとかで、実際にはアプリ自体は待ってるだけでCPUもメモリもスッカスカ、みたいな状態であっても。それ以外のリクエストは30msくらいで即答できるのだとしても。

Rainbows!の場合はUnicornをベースにしているけれども、EventMachineとかRevacatorみたいなイベント駆動ライブラリを使って1workerで並列にリクエストを処理できるようにしている。さっきみたいに長時間待ちになるリクエストが来てても、待ってる間に他の数十ms程度で返せるリクエストを受け取って処理して返せる。1プロセス1リクエストの枷が外れるので素のUnicornよりはworker数減らせてコンパクトになるかもしれない。そしてイベントループの中でアプリが動くのでAsyncSinatraみたいなフレームワークを使ってアプリ内で非同期処理を書ける。なので重いリクエストがある場合はこっちを使うといい、って話になる。

なんだ良いことづくめじゃん、Unicornいらない子じゃん、ってなるかと思えばそうでもない。Unicornがそもそもpreforkで1プロセス1リクエストでみたいな、ひどく割り切った、見ようによっては割と古典的な仕組みになってるのは、先行していたイベント駆動型のThinみたいなアプリケーションサーバに対しての「シンプルな方が作るのも管理も楽だし、なんだかんだでパフォーマンスも出るじゃん」みたいなアンチテーゼ的なところだったりするので、わざわざ複雑さを戻してるRainbows!はそういうメリットとのトレードオフになる。使いこなすと素敵な感じにはなるけど、worker増やせば解決する程度の話なら素のUnicornでも大して困らない。

追記

「Unicornシグナル送ればプロセス増やしたり減らしたりできるから、netstatとか見て詰まってそうだったら自動で増えるようにしてるー。滅多に増えないけど」っていう情報を貰った。TTINでプロセス増やす、TTOUでプロセス減らすとかできるので、自動で増減して欲しければなんか適当に監視スクリプト書いて適宜シグナル送るようにしとけばいいかも。まぁ手で送ってもいいし。

どうも、「iOS Advent Calendar 2011」5日目担当のfaultierです。つい最近使ったのでNSURLProtocolネタで。

NSURLProtocolって何?

Foundationフレームワークで最初から扱えるプロトコルはhttp、https、ftp、fileの4つ。これ以外のプロトコルでの通信をNSURLConnectionやNSURLDownloadなどで扱う場合や、特定のリクエストに限って特別な処理をしたい場合などに、NSURLProtocolを継承して登録することで使えるようになる。ちなみに、他のアプリからopenURLしたときにアプリを起動させるカスタムURLスキームとはまた別なので注意。こちらはアプリ内でURL Loading Systemを使うときにだけ影響するもの。

使い方

最低限必要なのは、+canInitWithRequest:、+canonicalRequestForRequest:、-startLoading、-stopLoadingの4つ。

まずはこんな感じで、どんなリクエストのときにそのURLProtocolをが処理するのかを決める。

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    return [[[request URL] scheme] isEqualToString:@"udon"];
}

ここでYESを返すとこのクラスがインスタンス化されて通信処理に進む。NOの場合は他に登録されているURLProtocolのこのメソッドが登録時の逆順に呼ばれて行く。この場合はudonというスキーム、例えばudon://marukame/bukkake/coolみたいなURLへのリクエストの時に処理をすることになる。別にこれは独自のスキームである必要ではなく、httpやfileなどをフックすることもできるし、特定のホストや特殊なヘッダが付いている時だけ処理するようなこともできる。

次は+canonicalRequestForRequest:。リクエストをcanonicalな形に変える必要がある場合はここで弄るのだけど、特に何もすることが無ければrequestをそのまま返してやればいい。

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

その後実際の通信が始まり、-startLoadingが呼ばれる。NSURLProtocolのオブジェクトは、id<NSURLProtocolClient>のclientと、NSURLRequestのrequestというプロパティを持っているので、requestからどんなリソースが必要なのかを判断して、clientに対してデータを返してやる、という形で通信の中継をする。NSURLProtocolClientのメソッドは大体NSURLConnectionのdelegateと対応しているので、NSURLConnectionでの通信を実装したことがあれば分かるはず。NSURLConnection側でキャンセルされたときはstopLoadingが呼ばれる。簡単な例としては以下のようになる。

- (void)startLoading
{
    // 本来非同期で通信するのだけど、
    // この例では単に文字列データを返すだけなので、
    // その場で返してしまう
    NSData *data = [@"うどんが食べたい" dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:
                              @"text/plain", @"Content-Type",
                              [NSString stringWithFormat:@"%d", [data length]], @"Content-Length",
                              nil];
    NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[self.request URL]
                                                              statusCode:200
                                                             HTTPVersion:@"1.1"
                                                            headerFields:headers];
    // NSURLResponseのオブジェクトを返し、
    [self.client URLProtocol:self
          didReceiveResponse:response
          cacheStoragePolicy:NSURLCacheStorageAllowedInMemoryOnly];
    // データを渡し、
    [self.client URLProtocol:self didLoadData:data];
    // 通信を終了。
    // 通信失敗の場合は -URLProtocol:didFailWithError:を呼ぶ。
    [self.client URLProtocolDidFinishLoading:self];
}

- (void)stopLoading
{
    // この例ではキャンセルしようが無い…
    // 内部で別なNSURLConnectionを使っていたり、
    // NSOperationQueueやGCDなどで非同期処理をしている場合、
    // ここでキャンセルの処理を実行する。
}

これだけだとudonスキームはまだURL Loading Systemに登録されていないので、適宜NSURLProtocolの+registerClass:、+unregisterClass:呼んであげることで使えるようになる。その独自スキームを使うUIViewControllerとか、あるいはもうクラスのloadメソッドで登録してしまうとか。

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [NSURLProtocol registerClass:[self class]];
    });
}

使いどころ

それどういうときに使うの?http以外のプロトコルとかそんな実装しないよ?と思うかもしれないけど、意外と使い道がある。UIWebView等での通信は裏側でNSURLConnectionを使っているので、WebView内での通信をフックしてアプリ側で弄ったりできるのだ。例えばCoreDataにあるデータをJSON形式にして返すようにしておくとか、JSからのトリガーでアプリ内のバックグラウンド処理を走らせるとか、画像をファイルキャッシュしておいてオフラインでも表示するとか、複数の異なる形式のWebAPIをプロキシして同じ形で扱えるようにしたり、ということも簡単にできる。

また、逆に「特定のリクエストに限って特別な処理をする」ではなく「特定のリクエストはスルーするけど他はブロックする」のような使い方もできる。最近(というには結構前からだけど)話題になったのは、iPhoneのWebViewにこういう脆弱性がある、という話。

何故デバイスのアドレス帳や着信履歴みたいなのの生のデータにJSからアクセスできる必要があるのか良く分からないし、おかしな仕様だと思うのでバージョンアップで塞がれるだろうとは思うけど、少なくともiOS5.0まではこの問題は残っているので、「アプリ内でUIWebViewにloadHTMLStringさせて、外部から取得したHTMLをfile://等のスキームで表示させている」ような場合には対処する必要がある。簡単な対策としてはきちんとbaseURLを設定しておけばいいのだけど、そうすると今度はCSS、JS、画像などのバンドル内部に持っているリソースが使えなくなる。「外部から取得したHTMLをアプリ内のUIWebViewで表示したいけど、アプリ内の安全なリソースにはアクセスさせたい」場合に、NSURLProtocolで通信をフックする仕組みが使える。

前者の記事だと、NSURLProtocolのサブクラスでバンドル内のファイルを開いて、そのデータを返してしまう、というやり方をしている。また、後者の記事やPhoneGapの実装では、NSURLProtocolの「登録時の逆順に対応できるかどうかチェックする」「YESを返せばそこで止まり、NOを返すと次のNSURLProtocolのチェックに進む」という性質を利用して、「ホワイトリストに載ってないリクエストの場合は、canInitWithRequest:でYESを返した上でエラーなり空レスポンスなりを返し、ホワイトリストに載っている場合はNOを返して通常のhttpやfileプロトコルとして処理させる」という方法でサンドボックス外のリソースにアクセスさせないようにしている。

まとめ

  • NSURLProtocolを使えばさくっと独自スキームを定義できるよ
  • WebViewみたいに細かく制御しづらいものの内部の通信もフックできるよ
  • セキュリティ面でも意味があるよ

という感じなので、是非試してみてください。

件の「ニュー速実験」ことOPERA実験のニュースがなかなか面白そうで気になる。ここんとこシュタゲにハマりまくってたタイミングで「CERNが!光速を!超えた!」なんてニュースが来たら、これはwktkせざるを得ませんなドゥフフ、なんだけど、まぁそれを差し引いても驚くべき話ではある。

OPERA実験ってなんだったのかなーと思って色々見てみたのだけど、ええと、「スイスからイタリアに向けて素粒子ニュートリノを打ったら、どうやら光速を超える速度で到達しているらしい。精密に測定してるはずだけど、計測誤差の範囲を超える数値で"速い"ようだし、統計的に十分な回数試行しても再現する。なんぞこれ」っていう話だったという理解でいいのだろうか。ふむ、なんか凄そうだ。もうちょっとキーワードを追ってみる。

  • ニュートリノ、と言えば、小柴先生が史上初めて自然に発生したニュートリノを観測してノーベル物理学賞を受賞しているわけだけれど、その小柴先生のチームは超新星爆発の際発生したニュートリノは可視光とほぼ同じ速さで到達していることを観測していて、今回のOPERA実験とは矛盾する。
  • 正の質量を持つ物質をどれだけ加速させても、理論上光速を超える速度は出ないとされている。今までにそういう物質や現象は確認されていない。
  • 特殊相対論に反しない形で、虚数の質量を持ち、エネルギーを失なえば失なうほど減速し、どんなに減速しても常に超光速であり光速以下にならないタキオンの存在は理論上仮定されているが、実験によって観測された例はいまのところ無い。
  • ニュートリノは質量を持つことが実証されているので、質量0や虚数質量の粒子ではない。件のOPERA実験チームもニュートリノ振動を観測するための研究をしているらしい。

みたいな。うーん。「とりあえず他の研究機関が同様の実験をしてみるまでは結論保留で、あと他の方法でこの現象を確認する良いアイディアあったらよろ」って段階っぽい。そりゃそうだよなぁ。素人目にも、少なくとも「アインシュタインの相対論は間違っていた!」とか「CERNはタイムマシンを開発しようとしている!」みたいな報道は早計すぎんだろと思う。「これでタイムマシンも作れちゃいますねー」なんて、コメント求められた専門家が言ったんだとしても冗談半分のリップサービスか、聞いてきた記者が理解できなくてセンセーショナルなところだけ抜き出して書いちゃった感。もちろんそれでも面白そうな話題であることに違いは無いんだけども。

それにしても折角面白そうな話題なのに解説読んでも半分も理解できないの寂しい。一体どこから勉強しなおせばちゃんと理解できるんだろ。それこそタイムマシンでも使って高校生くらいからやり直さないとだめかしら。誰か相対論とか場の量子論が何言ってるか分かるようになるまでに何が必要か教えてくだしあ。

あと全然関係ないけど「Operaは光速より速いブラウザ」って誰かが言い出す、あるいはもう言ってるに76円28銭賭ける。

↑このページのトップヘ