500 円玉貯金が N 万円貯まったので Thunderbolt Display を購入しました。
現在の開発環境の様子です。
画面が広いと Vim で複数ファイル広げたり、ファイル更新時にはその横でユニットテストが自動実行されたり、別のペインで tig や htop を実行しておくこともできるので便利ですね。
あとは最近少女革命ウテナ DVD-BOX を買ったんですが、これも大画面で観られるようになりました。
そんな大画面でつくった gem を紹介します。
Mr. Mongo とは
MongoDB の MapReduce の定義を Ruby の内部 DSL で記述し、実行するためのフレームワークです。
Ruby で、といっても map/reduce の関数は JavaScript です。
例えばワードカウントであれば、以下のような記述になります。
map/reduce を __END__ 以降に記述できるのが特徴です。
ここでは使われていませんが、finalize も同様に定義できます。
なお、__END__ 以降の読み込みについては先日開発した InlineTemplateLoader を使用しています。
何で DSL が必要か
ひとつは書き方・定義の仕方を統一するため、もうひとつはユニットテストのためです。
書き方を統一する
MongoDB 標準のやり方にしろ、各言語向けのドライバを使うにしろ、そのままだと人によって書き方がいろいろ異なります。
そこにこの Mr. Mongo を導入することで、「1 ファイル 1 ジョブ」というルールが強制されるようになります。
MapReduce をユニットテストするということ
MongoDB における MapReduce のユニットテストについては、ほとんど言及されることが無いと思いますが、やはり重要なんじゃないかと考えています。
上記のような単純なワードカウント程度の例ならともかく、大量のデータを、複雑に処理し、しかも長期的にメンテナンスを行うのであれば、回帰テストとしてのユニットテストが無くては安心できません。
また、テスト駆動で開発することでより効率よく開発できるケースもあるでしょう。
Mr. Mongo でどうユニットテストを書くか
これについては現状でもできなくはないのですが、フレームワークとしてのサポートがまだ貧弱な状態です。
mr_mongo-rspec-matcher なんてのを作って RSpec でのユニットテストをやりやすくする、というアイディアはあるものの、まだ実装できていません。
一応現在のコードベースでも MapReduce のユニットテストはできています。
MapReduce をオンメモリで実行し、その結果を連想配列と比較しています。
これをもっとフレームワークっぽいやり方でできるようになれば、いろいろ捗るんじゃないかと思っています。
あと、以前作った mongo_require.js を併用することで、MapReduce ジョブをある程度モジュール化して記述すれば、これもまたメンテナビリティに寄与すると思われます。
まとめ
MongoDB の MapReduce を効率よく開発・管理するためのフレームワーク Mr. Mongo について紹介しました。
おやすみなさい。
ちょっと CakePHP を使う機会があったのでソースコードを眺めていたところ、ちょいとしたバグが見つかりまして、すぐにパッチを書き Pull Request を行ったところ、ものの数時間でマージされました。
バグというのが、「alphaNumeric バリデータにおいて、改行で区切ってしまえばどんな文字列も通してしまっていた」ということです。
修正は以下の 1 コミットのみ。
Fix alphaNumeric validation · 14c81fe · cakephp/cakephp
preg_match や preg_replace をはじめ、PCRE を使った正規表現で、このような 1 行の文字列にバリデーションを行う際は、正規表現の修飾子として D (PCRE_DOLLAR_ENDONLY) を使用しないと、末尾の改行に対して検出漏れが発生してしまいます。
それどころか、m (PCRE_MULTILINE) なんてつけていると各行に対してパターンマッチングを行い、preg_match の場合はどれかひとつでもマッチすれば 1 を返すので、例えば以下のような文字列も OK、という恐ろしいことが起こってしまいます。
$result = preg_match('/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/mu', "abc\n<script>alert(1)</script>");
var_dump($result);
// => int(1)
あくまでユニットテストレベルでの検証ですが、このような恐ろしいことが起こっておりましたので、早いところ次のリリースが行われることを願うばかりです。
CakePHP 2.3.2 当たりで取り込まれることが予想されます。
こういう正規表現を書いていると徳丸先生方面から斧が飛んで来る可能性があります。
予め徳丸本等を読んで対策を行うことをオススメ致します。
あとは PHP のマニュアルとか PCRE 自体のマニュアルの PASSING MODIFIERS TO THE REGULAR EXPRESSION ENGINE 付近とか。
(なお、徳丸本の 81 ページによると ^ の代わりに ¥A を、$ の代わりに ¥z を使って、「行の」先頭・末尾ではなく「データの」先頭・末尾を示すようにせよ、とあります。)
なお、その他のバリデータも同様の問題をはらんでいる可能性もありそうですが、普段 CakePHP を使わない私としてはそこまで見切れないので、仕事等で CakePHP を利用している方は、是非とも contribute してみてはいかがでしょうか。
2 月はブログを書くことなく終わってしまいました。
3 月も半ばを過ぎようとしていますが、久々のブログ更新です。
ちょいとした Web アプリケーションを書くとき、やっぱり選んでしまうのは Sinatra。
とにかくサクっと作れるし、ファイルとかも少なくて済む。
中でもズボラ的に便利だと思うのがインラインテンプレートと呼ばれる機能です。
以下のように、プログラムの末尾にテンプレートをそのまま書けるというものです。
このインラインテンプレートがそれだけで gem 化されていれば、あらゆるフレームワークや DSL にこれを組み込めるのではないか、ということで作ってみました。
yuya-takeyama/inline_template_loader
inline_template_loader という名前で rubygems.org に登録済みですので、gem コマンドなり bundler なりでインストールできます。
使い方はこんな感じ。
InlineTemplateLoader.load でインラインテンプレートを Hash として読み込みます。
インラインテンプレートを読み込むファイルを load に引数として渡すこともできますが、省略時は caller を使ってバックトレースから呼び出し元を調べて、そのファイルから読み出すようにしています。
これを何らかのフレームワーク等に組み込む場合は、少し工夫が必要です。
普通は InlineTemplateLoader.load をラップして使うことになると思いますが、その場合は上記のように引数を省略してしまうと、ラッパーメソッドが存在するフレームワークのライブラリそれ自体からインラインテンプレートを読み込んでしまうことになります。
それだと困るので、引数に整数値を渡した場合は caller が返す配列のうち、整数値分だけ後の要素を呼び出し元とすることができます。
文だけだと訳がわからないと思うので、例を示します。
まずは DSL を用いた何らかのプログラム。
インラインテンプレートはこのファイルにあります。
次に InlineTemplateLoader をラップして何らかの処理を行う内部 DSL の処理系です。
このように load に整数値 (ここでは 4) を渡すことで、その文だけバックトレースを遡り、DslUsesInlineTemplateLoader.dsl の呼び出し元のファイルからインラインテンプレートを読むことができます。
来週あたりには、これを用いて MongoDB を操作する DSL なんかを紹介できればと思ってます。
そんじゃーね。
あけましておめでとうございます。
年末年始は実家にも帰らず、Kindle Paperwhite で本を読みあさったり、Hulu でプリティーリズムオーロラドリームを視て泣いたり、小林賢太郎の公演を DVD で視て笑ったり、あとは近所の土手を走ったりしてきました。
1km あたり 4 分をなかなか切れずにいます。
そんな感じでインプット中心に年末年始を過ごしてしまったわけですが、そろそろアウトプット期に転換していこうということで、みんな大好き fluentd に以下のような Pull Request を送ってみました。
Config DSL by yuya-takeyama · Pull Request #97 · fluent/fluentd
今のところ Proof of Concept レベルで、マージをお願いできるようなレベルにはなってないと思います。
こういうのどうっすかね、という感じで識者の皆様にコメントなど賜りたいと思っている次第です。
とりあえずは既存の設定ファイルも今まで通り使えるように、fluentd 起動時の -c オプションで指定した設定ファイルの拡張子が .rb だったときだけ、DSL として解釈するようになってます。
これによって fluent.conf は以下のような fluent.conf.rb として書けるようになります。
また、複数のサーバに転送する場合など、イテレータを使えれば、コピペ要らずで DRY な感じの記述ができます。
他にも、対象サーバの一覧を REST API から取得する、みたいな感じにしてみれば、fluentd のクラスタ構成の変更をする際は、サーバリストを管理するサーバに情報を登録して、各クラスタにシグナルを送って再起動すれば反映される、みたいな自動化も柔軟にできるんじゃないでしょうか。
さて、私自身は今のところ fluentd を使っているわけではなかったりします。
ここ最近はデータ分析・閲覧のための基盤を MongoDB や GrowthForecast 等を使って構築しているのですが、各サーバ感のログのやり取りが日次で rsync、という具合で、いちいち設定が面倒な上に、リアルタイム製にも欠けてしまいます。
そういう問題を解決する上で fluentd はすごく便利そうなので、今年はアレコレ調べたり contribute していければと思っている次第です。
それでは皆様今年もよろしくお願い致します。
MongoDB で使用する JavaScript 関数をモジュール化して Nodeunit でユニットテストしよう、という話です。
2012 年 12 月現在の Wikipedia によると MongoDB は CommonJS の処理系ということになっているようですが、CommonJS Spec Wiki にその名は見当たりません。
CouchDB は入ってるんですが。
MongoDB を CommonJS 処理系として見たとき、まず一番に辛いのが require 関数が無いことです。
(そもそも require が CommonJS の仕様において必須なのかどうかとかはよく知らない)
require が無くて何が困るかというと、MapReduce に使う関数なんかをモジュール化して書けないので、ユニットテストを書くのが困難、ということです。
そこで、MongoDB において JavaScript コードのモジュール化を促すものを作ってみました。
yuya-takeyama/mongo_require.js
MapReduce に使用するモジュールを作成する
mongo_require.js を使えば、CommonJS と同様のやり方で JavaScript モジュールを作成し、それを MongoDB 内で使用することができます。
それでは定番の WordCount をやってみましょう。
CommonJS のやり方に従って、exports オブジェクトのプロパティとして mapper と reducer の関数をそれぞれセットしています。
ちなみに、ここでは使用していませんが module.exports も使用できます。
これをモジュールとして読み込んで、実際に MapReduce を実行してみましょう。
まず、mongo_require.js を適当な場所に保存します。
そして、MapReduce を実行するのは以下のスクリプト。
はじめに mongo_require.js を読み込むことで、mongo_require() 関数を有効にします。
mongo_require() 関数は CommonJS の require 関数とほぼ同様に使用できます。
ここでの mr オブジェクトにはプロパティとして mapper と reducer それぞれの関数を持っているので、それをそのまま mapReduce 関数に渡しています。
実際に実行すると、以下のように正しく単語数が集計できていることがわかります。
ここではあらかじめ texts コレクションに文が入っていたものとしています。
このように、関数をモジュール化した上で、それを利用しての MapReduce ができました。
モジュールをユニットテストする
関数をモジュール化できたのであれば、それをユニットテストするのも容易になります。
ユニットテストには Node.js など、別の CommonJS 処理系の、既存のテスティングフレームワークを使用することができます。
フレームワークは何でもいいのですが、とりあえずシンプルなもの、ということで今回は Nodeunit を使用してみます。
個人的に Node.js でモジュールを書くときは Jasmine で BDD スタイルに書くのが好きですが、今回のようなシンプルなモジュールであれば、Nodeunit ぐらい素朴なフレームワークの方が向いているのではないでしょうか。
ここでのモジュールの読み込みには普通に require() 関数を使用します。
これを実行すると以下のような結果が得られます。
このユニットテストのポイントとしては以下が上げられます。
mapperEmits() 関数は第一引数として Mapper 関数、第二引数として処理するレコードを受け取り、そのレコードを処理したときに emit() されるレコードが第三引数と一致するかをチェックします。
(emit() と mapperEmits() については npm でパッケージ化することを考えています。)
ところで、グローバル関数に依存したテストを書くことは、本来であればアンチパターンとされています。
例えば大規模なプロジェクトにおいてはグローバル関数は極力避けることが望ましいのですが、ここではひとつの MapReduce をひとつの小規模なプロジェクトとして考えています。
プロジェクトの規模が小さければ、グローバル空間汚染によるコンテキストの複雑化もそれほどは問題にならないでしょう。
Reducer 関数については、通常は入力値を元に値を返すだけの参照等価な関数なので、特にこういった特別な関数を用意せずとも deepEquals() 関数でアサーションができます。
まとめ
mongo_require.js を使って MongoDB で使用する関数をモジュール化する方法と、そのモジュールを Nodeunit でユニットテストする方法について紹介しました。
MapReduce は元々 Mapper と Reducer というシンプルな二つの関数の組み合わせで大規模な計算を行う、というアイディアのもとに考えられています。
このシンプルな関数さえ正しく動作することが保証できれば、多少複雑な集計も安心して実装することができますね。ハピラキ。
レガシーコードと戦っていると、「このコードのどこをどう通ってこういう結果になってしまっているのか」がわからなくなることがあります。
初見でコードを理解する能力は、コードを読んできた経験が多ければ多いほど、向上するものだと思います。
とは言っても、構造化やモジュール化が適切でなく、スコープの長大なコードなどは、人間の限界を超えているものもあるでしょう。
ステップ実行のできる IDE などを使う、という選択もあると思いますが、僕は重厚な IDE を好みません。
もっと楽にできる方法で、コードパスを解析する方法があれば、ということで作ってみました。
yuya-takeyama/code_path_analyzer
元になっているのは、仕事の時にコード中にベタ書きした関数です。
Gist に公開したところはてブが 10 ぐらい付いたのと、次必要になったときにすぐ使えるようにしておきたかったので、ライブラリ化しました。
使い方
Composer でインストールできます。
Composer を使っている場合は気にする必要が無いのですが、例えば PHP 5.2 などの環境で Composer やそのオートローディングの仕組みが使えない、という場合を想定して、ファイルツリーを適当に配置したら、./src/Yuyat/CodePathAnalyzer/Registrar.php 1 ファイルを読み込むだけで、他に必要な全てのファイルを読み込むようになっています。
パフォーマンスを気にするのであればオートローダを使いたいところですが、デバッグ時しか使われないので、それぐらいの妥協はいいんじゃないでしょうか。
解析対象のできるだけ先頭に以下のように記述すれば、そのファイルを解析することができます。
解析結果は以下のようなテキストファイルとして出力されます。
先頭に + (プラス) が付いているのが、実際に実行された行です。
これを見ることで、どのようにこのプログラムが実行されたのか、を把握しやすくなります。
仕組み
XDebug のコードカバレッジ解析の仕組みを利用しています。
Yuyat_CodePathAnalyzer_Registrar::registerDefault() の実行時に xdebug_start_code_coverage() を実行することで XDebug のカバレッジ解析を有効にするのと同時に、register_shutdown_function() で終了時に解析処理をフックするようにしています。
解析処理の中では xdebug_get_code_coverage() を実行することで収集した解析データを XDebug から取得し、CodePathAnalyzer の各コンポーネントが協調して結果の出力を行います。
出力形式の変更について
現在はテキストファイルへの出力のみに対応していますが、HTML 形式などに出力できたほうがより見やすいでしょう。
AnalysisHandlerInterface を実装したクラスを用意することでそういった拡張にも行えるようにしていますが、個人的にはテキストファイルだけで充分だったので、詳細は割愛します。
要望があればその辺の情報もちゃんとドキュメント化するかもしれません。 (めんどくさくてやらないかもしれません)
そもそもコードパスって…
完成してから気づきましたが、これを持ってコードパス解析、というのはどうかなー、という気がしています。
コードパスというのは「コードの経路」ですから、「この行は実行されたか」といった点の情報ではなく、「どのような順番で実行されたか」という線の情報だと思うので、これだとコードパスとは言えないんじゃないかなーと。
XDebug には IDE でのステップ実行のためのインターフェイスもあったと思うので、それを使って、例えば Socket.IO なんかでブラウザ上に表現できればよりかっこいい!なんていう気はするのですが、今のところそこまでのものは必要としていないので、多分作らないと思います。
まとめ
気軽にコードパス (?) を解析できる CodePathAnalyzer について紹介しました。
そんなに気の利いたツールではないですが、この程度情報があればちょっと楽になる、ぐらいのときに使っていただければ幸いです。
昨日の ParallelHttp の話に引き続き、PHP でのバッチ処理のパフォーマンス改善の話です。
あと、昨日と同じく PHP 5.2 で使えるライブラリを開発した話でもあります。
バルクインサートとは
ひとことで言えば「複数のレコードをまとめてインサートすること」です。
例えば MySQL で言えば、
INSERT INTO `users` (`name`) VALUES ('foo');
INSERT INTO `users` (`name`) VALUES ('bar');
INSERT INTO `users` (`name`) VALUES ('baz');
ではなく
INSERT INTO `users` (`name`) VALUES ('foo'), ('bar'), ('baz');
というようにやることです。
これで何が嬉しいかというと、サーバにクエリを投げてその結果を受け取って、という往復が少なくて済むので、その分効率よくたくさんのレコードを INSERT でき、実行時間が節約できます。
このように手書きで SQL を書く分には何てこと無いですが、プログラムの中でやるとなると微妙に面倒だったりします。
バルクインサート専用ライブラリ Bulky
というわけで作りました。
Bulky は DB へのバルクインサートだけを行うためのライブラリです。
SELECT や UPDATE といったことは一切できません。
Perl とかだとバルクインサートのできるライブラリがいくつか見つかるのですが、PHP だと今のところ見つけられてません。
皆さんどうやっているんでしょうね。
Bulky を使うと、以下のように一見バルクインサートを思わせないコードで、バルクインサートを実行することができます。
この例では、10 万件のレコードを 50 件ずつバルクインサートしています。
Bulky によるバルクインサートの仕組み
$queue->insert() メソッドを読んだ瞬間は実際の INSERT は実行されません。
とりあえずはキューに溜めて、設定した数 (ここでは 50) に達したタイミングで自動的に $queue->flush() メソッドが呼ばれて 50 件分のバルクインサートが実行されます。
50 件に達しなかった場合も、$queue->flush() を明示的に呼べばキューに溜まっている分だけのレコードをバルクインサートします。
仮に明示的に呼ばなかったとしても、デストラクタで $queue->flush() を実行するようになっているので、暗黙的に全てのレコードが INSERT されるようになっています。
Bulky でのエラーハンドリング
上記の例では、$queue->insert() 呼び出し時にエラーハンドリングを行っていません。
何故かというと、そもそもできないからです。
$queue->insert() は特に値を返しません。
(引数のレコードのカラム数が一致しなければ例外は投げます)
INSERT が成功したかどうかは実際にバルクインサートが実行されるまでわかりません。
なので、予めエラーハンドラをコールバックとしてセットしておき、INSERT に失敗したときにはそれが呼び出されるようになっています。
バルクインサートでは 1 つでも失敗すると、そのクエリ中の全てのレコードが失敗になります。
この例でいうと、1 度の失敗は同時に 50 レコード分の INSERT に失敗したことになります。
エラーハンドラではその 50 レコード全てを配列で引数に受けるので、どのレコードの INSERT に失敗したのかをログに残すなりアラートを飛ばすなりできるようになっています。
MySQL 以外への対応について
この記事のタイトルには MySQL としていますが、Bulky の設計上は特に MySQL には依存していません。
DB 操作それ自体には様々なライブラリを使用できるよう、GoF パターンでいうところの Adapter Pattern を用いて実装しています。
現在のところは PdoMysqlAdapter というアダプタだけが実装されていて、これを使えばデータベース操作は PDO によって行われます。
PDO は様々なデータベースの差異を吸収してくれる組込みライブラリですが、敢えてここで MySQL としているのは、僕が他の DBMS でのバルクインサートをやったことが無いからです。
DBMS によっては同じ形式のクエリでは動かないかもしれないし、動くかもしれない、ということでとりあえずは自分に必要な MySQL 以外については放置しています。
簡単なベンチマーク
Bulky を使うことでどれだけパフォーマンスに違いが見られるか、ということで簡単なベンチマークを行いました。
使用したサーバは仕事で使っている開発用サーバです。
開発用サーバとはいえ、そのサーバ内ではいろんなものが動いていて、無風状態とは言えない適当なベンチマークなので、その辺りはご了承ください。
マシンの詳細なスペックも省略します。
また、適当なので time コマンドを使って、PHP スクリプトの実行時間を計測しています。
スクリプトの実行前には予め対象テーブルを TRUNCATE しているので、一応は平等な条件のもとで計測しています、と言えるんでしょうか。
まずは通常の INSERT の計測です。
何てことは無い、PDO でベタ書きの INSERT 文を実行するだけのものです。
結果は 1m25.381s、約 1 分半もかかってしまいました。
以下は、最初に掲載したサンプルコードを元に、バルクインサートの単位だけ調節しつつ計測したものです。
| 同時インサート数 | 処理時間 |
|---|---|
| 1000 | 44.256sec |
| 500 | 10.800sec |
| 200 | 9.478sec |
| 120 | 8.091sec |
| 100 | 7.242sec |
| 80 | 6.721sec |
| 50 | 5.508sec |
| 40 | 6.267sec |
| 30 | 6.956sec |
| 10 | 10.709sec |
| 1 | 1m30.200sec |
この環境では同時インサート数 50 前後でのバルクインサートが一番処理時間が短く、それより多くても少なくてもより多くの処理時間がかかってしまうことがわかります。
予想としては 500 や 1000 ぐらいが一番効率いいんじゃないか、とか思っていたんですが、意外と少ない方がいいようです。
詳しい検証は全くできていませんが、同時インサート数が多いときは何がネックになっているんでしょう?
クエリの組み立てロジックが適当なので、そこら辺の文字列操作がネックになっているのかもしれません。
または、MySQL サーバ側でも、あまりクエリが大き過ぎるとかえって非効率になったりするんでしょうか?
ともかく、今回の場合は同時インサート 50 のときのパフォーマンスで充分だったので、とりあえずはこのまま実運用に投入する方向で検討しています。
Bulky を使ってみる
ParallelHttp と同様、Packagist に登録してあるので、Composer を利用してインストールできます。
Composer をよく知らないという方はこのあたりの資料をご覧ください。
まとめ
MySQL による大量の INSERT を高速化する方法と、それを手軽に実現するライブラリ Bulky について紹介しました。
仕事でコードを書く機会が減りつつある今日この頃ですが、休日にこういう便利で着実に効率の上がるライブラリを開発しつつ、日々の仕事にフィードバックしていくのが 20 台後半に差し掛かった自分なりの生存戦略かなぁと考えています。
あと、今回は要件が PHP 5.2 だったのでニッチな感じになってしまいましたが、チャンスがあれば割とイケイケな OSS 方面にももっと貢献していきたいと考えているので、皆様今後ともよろしくお願い致します。
(React の件いろいろ放置してて本当に申し訳ございません…)
最近は Admiral Sir Cloudesley Shovell というバンドの 1st フルアルバム Don’t Hear It… Feat It をよく聴いています。
Sir Lord Baltimore のような、骨太なリフと、ブルージーでドラマ性もある楽曲が魅力のバンドですが、現在進行形で活動中のバンドです。
ストーナーロックとか好きな人は是非。
ここから本題です。
あるところに、Web API を数十万回単位で叩きまくる、PHP で書かれたバッチプログラムがあったとしましょう。
処理件数が増えるごとに処理時間が増大するので、いつしか 1 日経っても終わらないようになってしまいました。
そのプログラムは HTTP リクエストを直列に行っていたので、それを並列化させれば何とかなるのではないか、と考えて作ったのが以下のライブラリ。
念のため書いておくと、全然プロダクションレベルにはなってないので、その辺りは自己責任でご利用ください。
試してみる
Packagist に登録しているので、Composer を使用してインストールすることができます。
Composer をよく知らないという方はこのあたりの資料を参考にすると良いのではないでしょうか。
composer install してやると EDPS (エディプス) というキラキラネームのライブラリも一緒にインストールされます。
これは Événement という PHP 5.3 または 5.4 以降で動作するイベントディスパッチャライブラリを 5.2 でも動くように移植したものです。
これを使えば、以下のように、Node.js ライクなコールバックスタイルで HTTP リクエストを行い、そのレスポンスを処理することができます。
PHP で並列 HTTP リクエストを実現する方法
PHP で並列に HTTP リクエスト、といえば curl_multi_* 系の関数がおなじみで、ちょっとググるだけでも以下のような記事が見つかります。
今回作った ParallelHttp も同様に curl_multi_* 系の関数を使用しての並列リクエストを行っています。
また、僕は以前にも HTTP_Parallel というライブラリを作ったことがあって、これも curl_multi_* 系の関数を使用しています。
上記の記事は curl_multi_* 系関数の紹介ということもあって、わりとゴリゴリと実装されていて再利用しづらいので、オブジェクト指向なインターフェイスにラップしよう、というモチベーションのもとに作りました。
既存のやり方がダメだった理由
先に紹介した記事の方法や、それを応用した HTTP_Parallel では、今回の問題に対応することができませんでした。
これらに共通するのは、「リクエストは並列に行うが、レスポンスの処理は最後に全部まとめてやる」というところです。
HTTP リクエストが 1 つでも残っている限りは他のタスクを一切行わず、最後のレスポンスが返ってくるまでただ待ち続けることになります。
これではリクエストを並列化させているだけで、非同期処理とは言えないでしょう。
そこで ParallelHttp は以下のようなフローで HTTP リクエストと、そのレスポンスの処理を行っています。
リクエストを待つ間に、先に処理できるレスポンスを処理することで、待ち時間を有効に活用することができます。
数 10 件程度のリクエストなら大した差は出ないと思いますが、今回のように数 10 万規模のリクエストを行うとなると、かなりの差が出ることが予想されます。
処理件数によるさらなる問題
レスポンスの処理を逐一行うことで、処理効率を上げられそうな感じがしてきました。
が、問題はその手前にありました。
前のセクションに書いた通り、イベントループを開始すると、登録されたリクエストを同時に実行することになるので、素直に実装するとものすごい数のリクエストが全て同時に行われてしまいます。
そうなると以下のような問題が発生します。
これらについても解決しないと、今回の問題に ParallelHttp を使うことはできませんでした。
対策 1. 並列数を保てるようにする
同時に行うリクエストの数を設定として持たせ、最大でもその数までしか並列化させなければ、ローカルマシンにもリモートマシンにも優しくなります。
ParallelHttp ではイベントループの内部にキューを持っており、並列数が最大になるまでは curl_multi リソースにリクエスト情報を登録していきます。
curl_multi に登録したリクエスト情報が最大に達すると、その後のリクエストはとりあえずキューの中にためておいて、1 つでもレスポンスが返ってきたらそれを処理しつつ、キューから次のリクエスト情報を curl_multi リソースに登録する、という方法を採りました。
イベントループの生成時に以下のようなオプションを指定することで、並列数を指定することができるようになります。
デフォルトでは 10 になっているので、必要に応じて調節することで、さらなるパフォーマンスを得たり、マシンリソースに気をつけて処理を行ったりすることができます。
これで、前のセクションで示した問題のうち最初の 2 つが解決されました。
対策 2. リクエスト情報の登録を少しずつにする
最後に残るのは、リクエスト情報とコールバックのために、大量のメモリを確保する必要がある、という点です。
結論からいうと、これは ParallelHttp の実装だけで解決することはできませんでした。
そこで、ParallelHttp の使い方を工夫することにしました。
例えば、処理対象の URL がデータベースに 30 万件入っていて、それらの全てにリクエストを行う場合を考えてみましょう。
memory_limit の設定値等にもよりますが、普通は $loop->run() の手前で、メモリ使用量が上限を超えて強制終了になるでしょう。
URL 30 万件程度ならメモリを多めに確保しておけば何とかなりますが、ParallelHttp の内部で生成しているリクエスト情報を保持したオブジェクトであったり、コールバックに使う無名関数であったりでメモリを大量に消費します。
コールバックに使う関数が単一でよければループの手前で変数にいれておき、それをコールバックとして登録するようにすれば、コールバック分のメモリは節約できます。
ですが、クロージャを使用する場合等はリクエストごとにユニークなコールバックを生成することになるので、そういうわけにもいきません。
URL を一度にまとめて取得し、その全てをイベントループに登録するのではなく、少しずつの URL をイベントループに登録し、そのリクエストが全て終わった段階で新たに URL を登録していく… というのを延々繰り返すようにしてみます。
これは擬似的な例ですが、実際にこういうのを作ってみてとりあえず 1 週間はまともに動かすことができました。
ここで UrlIteratorIterator としているものについて、具体的に説明すると、外側のイテレータはデータベースから URL の一覧を LIMIT 1001 で取得し、1000 件分の URL を内側のイテレータとして返します。
LIMIT 1001 としたのはまだデータベース内に次の値があるかどうかをチェックするためで、1001 番目の要素に値があれば、オフセットを適当に指定しつつ次の 1001 件を取得する、といったことを繰り返しています。
まとめ
PHP 5.2 で動作する非同期 HTTP 処理ライブラリ ParallelHttp について紹介しつつ、その中で直面した問題と、その解決について説明しました。
高いパフォーマンスを得る上で非同期処理は大変魅力的ではありますが、それはそれで別の問題が生まれるので、一筋縄では行きません。
とはいえ、筋のいいライブラリであったり、実装パターンを使用することである程度は緩和することができるでしょう。
今回作った ParallelHttp については React や Node.js の標準ライブラリのコードを大いに参考にさせていただきました。
また、前にブログで紹介した AsyncMysql を実装した経験も大いに活かすことができました。
いろんな実装パターンを学んで、フレームワークや言語にとらわれないプログラミングスキルを身につけて行きたいと感じました。
特別何かする必要があるわけではないんですが、最新の php-build の master ブランチに 5.5snapshot の定義ファイルが入りました。
これで Generator や finally が使えます。
しかしながら World domination は含まれないことになりました。
残念でしたね。
GitHub 上の php/php-src の HEAD を .tar.gz で落としてきてるだけなので、運悪くビルドが壊れていれば入らないこともあると思います。
個人的には前にも紹介した方法で phpenv と連携させて phpenv install 5.5snapshot などとしてインストールしています。
なお、最新の php-build は GitHub 上の好きなリポジトリやブランチを選んでビルドできるようになっています。
例えば以下のような定義ファイルを /usr/local/share/php-build/definitions 辺りに入れてやれば、リスト内包表記の実装された魔改造 PHP がビルドできます。
また、php-build を最新のバージョンにアップグレードする方法については、以下の記事が参考になります。
個人的には面倒なんでまっさらにインストールし直したりしています。 (特に秘伝の設定ファイルとか書いてたりするわけではないので)
取り急ぎ、報告までとさせていただきます。
以上、何卒よろしくお願い致します。
個人的な日記として書きます。
発表についてのフォローとかは特にありません。
去年に続き二年連続で枠をいただけたので、話してきました。
去年は 5 分間に 26 枚のスライドを詰め込みましたが、今年は 3 分間に 34 枚を詰め込みました。
内容については、今日たまたま見ていた PyCon JP 2012 の gevent についての発表が基礎からわかりやすく解説されていて素晴らしかったので、そちらを観るのがいいのではないでしょうか。
gevent と gunicorn については気になったのでソースコードを読んでみようと思いますし、コルーチンベースということなので PHP 5.5 なら Generator を使って同様のアプローチができないか、とかいろいろワクワクしてしまいますね。
他の方の発表を観た中では、最先端 Web 開発の話がダントツで良かったです。
特に「誰もこれない場所へ行くのではなく誰もがこれる場所を作る」というのは心に刺さる名フレーズだったと思います。
あとは Pull Request で仕事を進めていくのも凄くうらやましいですし、自分の会社でもそういう風にしていけたらと感じています。
ボクも元々はこういう日々の開発についての話がしたかったのですが、まだ自信を持って「成果があった」と言い切れる状態では無いので、他の人と被らなさそうなところで話を選んでみました。
この発表では「フルスタックフレームワークは夢」という話もあり、ボク自身も同じことを強く感じています。
最近は週末に単機能ライブラリを開発して GitHub に公開しておき、平日にそれを社内のコードベースに取り込む、みたいなことをしているのですが、諸般の事情によりいずれも PHP 5.2 で書かれているので、PHP 5.4 まで stable としてリリースされている今ではあんまりウケないんじゃないか、と思いました。
ところが今回のカンファレンスの基調講演によると PHP 5.2 はまだまだ使われているらしく (というかこのグラフ的には一番多い)、それらについても機会があれば話してみたいと思いました。
エンジニアになって 4 年目、勉強会で人前に出て発表するようになってようやく 1 年が経ちました。
(そういえば社外でまともに発表したのは去年の PHP カンファレンスが初めてでした)
振り返ってみると、便利なツールであったりフレームワークであったりの話ばかりで、より泥臭い実践的なノウハウについての話がほとんどできていません。
今の自分に足りないもの、そして仕事の中で求められているものはそこにある気がしているので、来年こそはそういう発表をしてみたいと思いました。
(それにはそれなりの成果が先立っている必要があるわけですが)
最後に、PHP カンファレンス 2012 のスタッフの皆様、素晴らしいイベントをありがとうございました。
GW 2 日目もバンドラーにジェムやレイルズアプリと戦っていました.
主に以下の記事を参考にしています.
古いバージョンで動いている Redmine をアップグレードする.
具体的には 0.9.3 を サーバは WEBRick データベースは SQLite という構成で動かしていて, 当初はこれでも十分だったけど, 年数が経ってくるにつれて辛くなってきたので 1.4.1 で Apache x Passenger で MySQL という構成にする.
個々のステップの前に, やり遂げるまでにぶつかった問題点を先に挙げる.
データ移行については, 移行対象のデータが割と大きかったせいで試行錯誤の度に時間がかかってしまった.
プラグインの仕様変更については, Rails 力も Redmine 力も低い自分にとってはどうしようかという感じだったが, プラグイン作者様が既に対応してくれていたり, 対応されていないものでも比較的容易に対応できたので何とかなった.
前日に Redmine/Rails 以前の問題を解決していおいたおかげで, そういった問題に悩まされることが無かった点は良かった.
試行錯誤した結果, 以下の手順でうまくいった.
なお, サーバ構成や, 使用するツール (Bundler とか) については前日の記事と同様としている.
これで今のところは問題無く動作している.
最初は rake db:migrate の前に yaml_db によるデータ変換を行ったりしていたが, これだと何故か rake db:migrate_plugins のときにエラーになってどうしようもできなくなった.
エラーメッセージを見る限りでは, 既にある Code Review プラグイン用の code_reviews テーブルを再度作成しようとして失敗していることはわかった.
でも何故そんな問題が起こるのかはよくわからなかった.
既に実行された migration は schema_migrations テーブルに記録されるからこういうことは起こらないようになっているものかと思っていたのだけど.
ともかく rake db:migrate を先に一通りやって, スキーマを最新の状態にしてから MySQL に変換することでうまくいくようになった.
前述の通り yaml_db を使っている.
以下の記事が参考になった.
yaml_db 自体は Redmine だけじゃなくて Rails アプリ一般に使えるようなので便利だと思った.
Rails3 でも使えるのかはよく知らない.
Redmine 1.4 でルーティングに関する仕様変更が行われ, ほとんどのプラグインで対応が必要らしい.
これまでは Redmine 全体の ./config/routes.rb で行われていたルーティングが, プラグインごとの ./config/routes.rb に委譲されるようになった, ということらしい.
Wiki Extensions や Redmine Code Review プラグイン では対応が行われていたので, 新しいバージョンにアップグレードするだけで解決した.
ゴンペルたん については対応が行われていなかったが, 以下のような ./config/routes.rb を用意することでとりあえず解決したようだ.
ActionController::Routing::Routes.draw do |map|
map.connect 'projects/:id/gompertan/:action', :controller => 'gompertan'
end
ゴンペルたんのようにシンプルなプラグインであればこれで大体解決するらしい.
いつの間にかガントチャートのチケットがプロジェクトやバージョンでグループ化されるようになっていた.
開発者たちの努力によるものだということは想像できるが, 開始日や期日でソートができないと辛い.
正直言って改悪だと感じた.
デフォルトは従来の開始日ソートに戻して欲しい.
古いバージョンで動いている Redmine をアップグレードしたいので, 必要な手順等について調べる.
でもまずは新規インストールができるようになる.
以下のような構成にします.
さくら VPS 月額 980 円でメモリ 1GB HDD 100GB とかすごいですね.
以下の記事を参考にしました.
基本的にグローバルな環境をなるべく汚さないことを意識しています.
GCC などをインストールする.
$ sudo yum -y update
$ sudo yum -y install gcc kernel-devel zlib-devel openssl-devel readline-devel curl-devel libyaml-devel
CentOS は大体に置いて, 提供されるパッケージのバージョンが古い印象がありますね.
そのあたりの事情について以下の記事が大変参考になりました.
つまりは EPEL というリポジトリを使うようにすると, CentOS でも割と新しめのバージョンのパッケージを, 割と安心してインストールできるということでしょうか.
というわけで入れてみました.
$ sudo rpm -Uvh http://ftp.jaist.ac.jp/pub/Linux/Fedora/epel/5/i386/epel-release-5-4.noarch.rpm
先に紹介した記事同様, デフォルトでは使用しないように /etc/yum.repos.d/epel.repo で epel の enabled を 0 にしています.
$ sudo yum -y install mysql-server mysql-devel
設定は基本的にブログ記事と同様.
ユーザ名・データベース名は redmine とした.
$ sudo yum -y install httpd httpd-devel
$ sudo yum -y install git --enablerepo epel
CentOS 5 だと Ruby 1.9 をパッケージで入れることはできなさげなので, ruby-build と rbenv を使ってホームディレクトリ内にインストールすることにする.
それぞれのインストール手順は README の通りなので省略する.
ruby-build は root としてインストールし, rbenv は redmine ユーザとしてインストールする.
ruby-build と rbenv のインストールが完了したら rbenv で Ruby 1.9.3 をインストールする.
$ rbenv install 1.9.3-p125
$ rbenv global 1.9.3-p125
Redmine が Bundler に対応したのでそれも入れる.
$ gem install bundler --no-ri --no-rdoc
GitHub のミラーリポジトリから入れる.
ホームディレクトリなどにインストールしてしまうと Apache から実行するときに困るので, 今回は /var/redmine にインストールする.
$ cd /var
$ sudo mkdir redmine
$ sudo chown redmine:redmine redmine
$ cd redmine
$ git clone https://github.com/redmine/redmine.git .
$ git checkout 1.4-stable
./config ディレクトリの設定については概ねブログ記事の通りだが, Ruby 1.9 の場合は adapter を mysql2 にしないといけないことに注意が必要.
Bundler を使う.
元の記事では 指定していないが –path オプションを指定してローカルディレクトリにインストールしている.
(指定しないと gem コマンドでインストールするのと同じ領域にインストールされてしまう)
yum で入る ImageMagick のバージョンが古くて RMagick が入れられないので, rmagick も無効化.
Optional な gem なので問題無い.
$ bundle install --without development test postgresql sqlite rmagick --path vendor/bundler
元の記事では rake を直接実行しているが, gem を ./vendor にインストールしているので bundle exec rake する必要がある.
$ bundle exec rake generate_session_store
$ bundle exec rake db:migrate RAILS_ENV=production
Passenger も Bundler で入れてみる.
Gemfile.local を以下のようにする.
gem "passenger"
そしてインストール.
$ bundle update
$ bundle exec passenger-install-apache2-module
適当に進めると mod_passenger がビルドされる. Passenger の設定は /etc/httpd/conf.d/passenger.conf に記載した.
基本的にはブログ記事の通り.
mod_passenger.so 等に関する設定は bundle exec passenger-install-apache2-module –snippet すれば確認できる.
VirtualHost 等を適当に設定する.
DocumentRoot を ./public (もちろん実際はフルパス) に指定するぐらいで OK.
とりあえず普通のインストールができたので, 次は古いバージョンからのアップグレードとか, Bundler を使ったプラグインのインストールとかについて調べる.
2011 年の年末に HashDoS というのが話題になった.
要するにハッシュテーブルのキーのハッシュ値を意図的に衝突させ, 非効率な挿入を行わせることで, 効率的にサービスを妨害する, というものだ.
ということをドヤ顔で書いてはいるが, 年末の時点ではこの HashDoS の原理については理解しておらず, 「データ構造を偏らせて計算量を増やすんだろう」ぐらいの漠然としたイメージしか無かった.
その後, PHP の HashTable 構造体や, 一般的なハッシュテーブルの実装について調べることで, HashDoS の原理がわかってきた.
ハッシュテーブルについては, いくつかの解説ページを見ながら, サンプルコードを Ruby に翻訳することで学習した.
PHP の HashTable 構造体は双方向リストを用いたハッシュテーブルとなっている.
キーのハッシュ値を元に要素を格納するスロットが決まり, そのスロットに既に要素が存在する場合は, そのスロット内の双方向リストの先頭に値を挿入する.
…という説明では上手く伝えきれないので, これを可視化する PHP 拡張を書いた.
hashtable_dump() 関数は引数として array 型の値を受け取り, HashTable 構造体レベルで内部の情報を出力する.
<?php
hashtable_dump(array(1, 2, 3, 4, 5, 6, 7, 8));
/*
nTableSize: 8
nTableMask: 7
nNumOfElements: 8
nNextFreeElement: 8
pListHead: 0
pListTail: 7
**arBuckets:
0 => [0, NULL]
1 => [1, NULL]
2 => [2, NULL]
3 => [3, NULL]
4 => [4, NULL]
5 => [5, NULL]
6 => [6, NULL]
7 => [7, NULL]
*/
いずれも HashTable 構造体のメンバに対応するものだ. 出力している内容は以下の通り.
配列が空のときは, 以下のようになる.
<?php
hashtable_dump(array());
/*
nTableSize: 8
nTableMask: 7
nNumOfElements: 0
nNextFreeElement: 0
pListHead: NULL
pListTail: NULL
**arBuckets:
0 => [NULL]
1 => [NULL]
2 => [NULL]
3 => [NULL]
4 => [NULL]
5 => [NULL]
6 => [NULL]
7 => [NULL]
*/
空なので, リストの先頭も末尾も NULL を指し, いずれのスロットにも値が無い. (NULL しか無い)
値の格納先のスロットは以下のようなビット演算で算出される.
hashKey & nTableMask
これは 0 以上 nTableMask 以下になる.
最初の例のように, 連番をキーに順番に値を挿入した場合は, 各スロットに値が均等に振り分けられる.
しかし, 以下のような値の場合, ひとつのスロットに値が偏る.
<?php
hashtable_dump(array(0 => 1, 8 => 2, 16 => 3, 24 => 4, 32 => 5, 40 => 6, 48 => 7, 56 => 8));
/*
nTableSize: 8
nTableMask: 7
nNumOfElements: 8
nNextFreeElement: 57
pListHead: 0
pListTail: 56
**arBuckets:
0 => [56, 48, 40, 32, 24, 16, 8, 0, NULL]
1 => [NULL]
2 => [NULL]
3 => [NULL]
4 => [NULL]
5 => [NULL]
6 => [NULL]
7 => [NULL]
*/
ハッシュテーブルのスロット数が 8 であれば, キーとして 8 の倍数の要素だけを挿入することで, いずれもハッシュ値が 0 となり, ひとつのスロットに値が集中してしまう.
ここからキーが 0 の要素を探索する場合, 全ての要素を操作して 8 番目にならないと辿り着けない.
線形検索をリッチに実装しただけのものになってしまっている.
ここにもうひとつ要素を追加すると次のようになる.
<?php
hashtable_dump(array(0 => 1, 8 => 2, 16 => 3, 24 => 4, 32 => 5, 40 => 6, 48 => 7, 56 => 8, 64 => 9));
/*
nTableSize: 16
nTableMask: 15
nNumOfElements: 9
nNextFreeElement: 65
pListHead: 0
pListTail: 64
**arBuckets:
0 => [64, 48, 32, 16, 0, NULL]
1 => [NULL]
2 => [NULL]
3 => [NULL]
4 => [NULL]
5 => [NULL]
6 => [NULL]
7 => [NULL]
8 => [56, 40, 24, 8, NULL]
9 => [NULL]
10 => [NULL]
11 => [NULL]
12 => [NULL]
13 => [NULL]
14 => [NULL]
15 => [NULL]
*/
要素数が 9 のときは, テーブルの大きさが 8 の 2 倍の 16 に拡張されるため, 偏りが少し解消される.
以下の関数を使うと, テーブルの拡張も考慮しつつ非効率な HashTable を構築することができる.
<?php
hashtable_dump(hashdos(128));
function hashdos($n) {
$tableSize = 8;
while ($tableSize < $n) {
$tableSize *= 2;
}
$arr = array();
for ($i = 0; $i < $n; $i++) {
$arr[$tableSize * $i] = NULL;
}
return $arr;
}
/*
nTableSize: 128
nTableMask: 127
nNumOfElements: 128
nNextFreeElement: 16257
pListHead: 0
pListTail: 16256
**arBuckets:
0 => [16256, 16128, 16000, 15872, 15744, 15616, 15488, 15360, 15232, 15104, 14976, 14848, 14720, 14592, 14464, 14336, 14208, 14080, 13952, 13824, 13696, 13568, 13440, 13312, 13184, 13056, 12928, 12800, 12672, 12544, 12416, 12288, 12160, 12032, 11904, 11776, 11648, 11520, 11392, 11264, 11136, 11008, 10880, 10752, 10624, 10496, 10368, 10240, 10112, 9984, 9856, 9728, 9600, 9472, 9344, 9216, 9088, 8960, 8832, 8704, 8576, 8448, 8320, 8192, 8064, 7936, 7808, 7680, 7552, 7424, 7296, 7168, 7040, 6912, 6784, 6656, 6528, 6400, 6272, 6144, 6016, 5888, 5760, 5632, 5504, 5376, 5248, 5120, 4992, 4864, 4736, 4608, 4480, 4352, 4224, 4096, 3968, 3840, 3712, 3584, 3456, 3328, 3200, 3072, 2944, 2816, 2688, 2560, 2432, 2304, 2176, 2048, 1920, 1792, 1664, 1536, 1408, 1280, 1152, 1024, 896, 768, 640, 512, 384, 256, 128, 0, NULL]
1 => [NULL]
2 => [NULL]
3 => [NULL]
4 => [NULL]
5 => [NULL]
6 => [NULL]
7 => [NULL]
8 => [NULL]
9 => [NULL]
10 => [NULL]
11 => [NULL]
12 => [NULL]
13 => [NULL]
14 => [NULL]
15 => [NULL]
16 => [NULL]
17 => [NULL]
18 => [NULL]
19 => [NULL]
20 => [NULL]
21 => [NULL]
22 => [NULL]
23 => [NULL]
24 => [NULL]
25 => [NULL]
26 => [NULL]
27 => [NULL]
28 => [NULL]
29 => [NULL]
30 => [NULL]
31 => [NULL]
32 => [NULL]
33 => [NULL]
34 => [NULL]
35 => [NULL]
36 => [NULL]
37 => [NULL]
38 => [NULL]
39 => [NULL]
40 => [NULL]
41 => [NULL]
42 => [NULL]
43 => [NULL]
44 => [NULL]
45 => [NULL]
46 => [NULL]
47 => [NULL]
48 => [NULL]
49 => [NULL]
50 => [NULL]
51 => [NULL]
52 => [NULL]
53 => [NULL]
54 => [NULL]
55 => [NULL]
56 => [NULL]
57 => [NULL]
58 => [NULL]
59 => [NULL]
60 => [NULL]
61 => [NULL]
62 => [NULL]
63 => [NULL]
64 => [NULL]
65 => [NULL]
66 => [NULL]
67 => [NULL]
68 => [NULL]
69 => [NULL]
70 => [NULL]
71 => [NULL]
72 => [NULL]
73 => [NULL]
74 => [NULL]
75 => [NULL]
76 => [NULL]
77 => [NULL]
78 => [NULL]
79 => [NULL]
80 => [NULL]
81 => [NULL]
82 => [NULL]
83 => [NULL]
84 => [NULL]
85 => [NULL]
86 => [NULL]
87 => [NULL]
88 => [NULL]
89 => [NULL]
90 => [NULL]
91 => [NULL]
92 => [NULL]
93 => [NULL]
94 => [NULL]
95 => [NULL]
96 => [NULL]
97 => [NULL]
98 => [NULL]
99 => [NULL]
100 => [NULL]
101 => [NULL]
102 => [NULL]
103 => [NULL]
104 => [NULL]
105 => [NULL]
106 => [NULL]
107 => [NULL]
108 => [NULL]
109 => [NULL]
110 => [NULL]
111 => [NULL]
112 => [NULL]
113 => [NULL]
114 => [NULL]
115 => [NULL]
116 => [NULL]
117 => [NULL]
118 => [NULL]
119 => [NULL]
120 => [NULL]
121 => [NULL]
122 => [NULL]
123 => [NULL]
124 => [NULL]
125 => [NULL]
126 => [NULL]
127 => [NULL]
*/
これを応用すると, HashDoS により効率よく Web サーバを落とすことができてしまう可能性がある.
なぐり書きブログなので特にまとめとかは無い.
ひたすら Vim でコードリーディングに疲れてきたので CGDB を導入.
Homebrew にあったのですぐできた.
$ brew install cgdb
Warning: It appears you have MacPorts or Fink installed.
Software installed with other package managers causes known problems for
Homebrew. If a formula fails to build, uninstall MacPorts/Fink and try again.
==> Downloading http://downloads.sourceforge.net/project/cgdb/cgdb/cgdb-0.6.5/cgdb-0.6.5.tar.gz
######################################################################## 100.0%
==> Downloading patches
######################################################################## 100.0%
######################################################################## 100.0%
==> Patching
patching file various/util/src/pseudo.c
patching file various/rline/src/Makefile.in
patching file various/rline/src/rline.c
==> ./configure --disable-debug --prefix=/usr/local/Cellar/cgdb/0.6.5 --with-readline=/usr/local/Cella
==> make install
ln: dir: Permission denied
Error: The linking step did not complete successfully
The formula built, but is not symlinked into /usr/local
You can try again using `brew link cgdb'
==> Summary
/usr/local/Cellar/cgdb/0.6.5: 9 files, 396K, built in 28 seconds
エラーが出はいるが正常に /usr/local/bin/cgdb ができており, 問題なく cgdb コマンドを実行することができた.
何でしょうね.
実際のデバッグ作業については以下の記事を参考にした.
上部の画面でブレークポイントの設定やソースコードリーディングを行いながら, 下部の画面ではコマンド入力を行い, 変数の中身を覗いたりできた.
これは PHP の count 関数の中で HashTable 構造体を覗いている様子.
構造体のメンバを参照するときに, s->m でも s.m でも特に変わらないようだ. (マジで?)
連結リストを辿って行くような操作もかなり簡単にできる.
ただ, GNU screen 上で起動すると画面が崩れてしまうので, 仕方なく iTerm2 を別途起動して, そこで実行している.
何とかならないものものか. (CGDB に限らないけど)
VM に入れている Ubuntu 上でも aptitude で簡単にインストールできたので, 環境を用意する手間はほとんど無いと言っていい.
これから活用していきたい.
今回の記事でも GitHub における php/php-src の commit c5d10ddda394b573dfaea1380285e1dd5f3c0d50 を前提としている.
前回は HashTable 構造体には nNumOfElements というメンバがあり, PHP の count 関数ではその値を読んでいることがわかった.
つまり, 配列の要素数が変わるタイミングで, nNumOfElements の値は書き変わるはずだ.
今回はその辺りに付いて読んでみる.
./Zend/zend_hash.c の中に, 以下のようなマクロ, 関数を見つけた.
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent) _zend_hash_init((ht), (nSize), (pHashFunction), (pDestructor), (persistent) ZEND_FILE_LINE_CC)
ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
uint i = 3;
SET_INCONSISTENT(HT_OK);
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = 1 << i;
}
ht->nTableMask = 0; /* 0 means that ht->arBuckets is uninitialized */
ht->pDestructor = pDestructor;
ht->arBuckets = (Bucket**)&uninitialized_bucket;
ht->pListHead = NULL;
ht->pListTail = NULL;
ht->nNumOfElements = 0;
ht->nNextFreeElement = 0;
ht->pInternalPointer = NULL;
ht->persistent = persistent;
ht->nApplyCount = 0;
ht->bApplyProtection = 1;
return SUCCESS;
}
zendhash_init のパラメータの最後についている ZEND_FILE_LINE_DC はソースコードのファイル名, 行数を引数として渡すマクロのようだ.
デバッグ時のみ渡されるようになるが, zend_hash_init マクロを呼ぶ限りは, そういったことは意識する必要が無い.
./Zend/zend.h で定義されている.
/* 一部略している */
#if ZEND_DEBUG
#define ZEND_FILE_LINE_D const char *__zend_filename, const uint __zend_lineno
#define ZEND_FILE_LINE_DC , ZEND_FILE_LINE_D
#define ZEND_FILE_LINE_C __FILE__, __LINE__
#define ZEND_FILE_LINE_CC , ZEND_FILE_LINE_C
#else
#define ZEND_FILE_LINE_D
#define ZEND_FILE_LINE_DC
#define ZEND_FILE_LINE_C
#define ZEND_FILE_LINE_CC
#endif /* ZEND_DEBUG */
zend_hash_init はその名前からすると, HashTable の初期化に使用するものだと思われる.
nTableSize メンバには HashTable の大きさと思われる値が代入されており, 0x80000000 は超えないようにされている.
そのあとはパラメータや定数をそのまま代入しているだけだ.
ただし, pHashFunction は特にどのメンバにもセットされておらず, 使われていないようだ.
zend_hash_init が呼ばれている箇所を grep してみても, どれも NULL を渡しているようだ.
配列の初期化時点では空なので, 当然 nNumOfElements は 0 だ.
nNextFreeElement も 0 となっているが, これは PHP で $arr[] = “foo” などとしたときに暗黙的に指定されるキーだろうか.
また, pListHead や pListTail のような, リスト要素を指すメンバにも当然のごとく NULL で初期化されている.
そして, pInternalPointer は, ./Zend/zend_hash.h の HashTable 構造体を定義している箇所のコメントによると, Used for element traversal とされている.
恐らく foreach などで array を走査するときに, 現在の要素を保持しておくためのものだろうか.
これらのメンバが配列操作時にどのように扱われているか, 見ていこう.
./Zend/zend_hash.c に zendhash_index_update_or_next_insert という関数がある.
いかにも $arr[0] = “foo” といった操作時に呼ばれてそうだ.
grep してもあまりヒットしないが, ./Zend/zend_hash.h 上にこのようなマクロがあった.
ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC);
#define zend_hash_index_update(ht, h, pData, nDataSize, pDest) \
_zend_hash_index_update_or_next_insert(ht, h, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
#define zend_hash_next_index_insert(ht, pData, nDataSize, pDest) \
_zend_hash_index_update_or_next_insert(ht, 0, pData, nDataSize, pDest, HASH_NEXT_INSERT ZEND_FILE_LINE_CC)
zend_hash_index_update も zend_hash_next_index_insert も zendhash_index_update_or_next_insert を呼んでおり, それぞれのマクロは以下の点において違うようだ.
zendhash_index_update_or_next_insert 関数を見てみよう.
日本語のコメントは全て私が書き込んだものだ.
ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
uint nIndex;
Bucket *p;
#ifdef ZEND_SIGNALS
TSRMLS_FETCH();
#endif
IS_CONSISTENT(ht);
CHECK_INIT(ht);
/**
* flag が「次に挿入」モードであれば,
* HashTable の次の空き要素を使用する.
*/
if (flag & HASH_NEXT_INSERT) {
h = ht->nNextFreeElement;
}
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
while (p != NULL) {
/**
* nKeyLength が 0 のとき, つまり数値がキーである場合.
* そして, キーとして指定した h 番目の要素を探している.
*/
if ((p->nKeyLength == 0) && (p->h == h)) {
/**
* 次への挿入, または追加モードのとき,
* 既にそのキーが存在すればエラー.
*/
if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) {
return FAILURE;
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_index_update: p->pData == pData\n");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
/**
* 更新モードのときは, 既にあった要素内の値のみを,
* HashTable に指定されたデストラクタで開放する.
*/
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
UPDATE_DATA(ht, p, pData, nDataSize);
HANDLE_UNBLOCK_INTERRUPTIONS();
if ((long)h >= (long)ht->nNextFreeElement) {
ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX;
}
if (pDest) {
*pDest = p->pData;
}
/* 既存の要素を更新した場合はここで終了. */
return SUCCESS;
}
/* 連結リストの次を辿る. */
p = p->pNext;
}
/* 新しく挿入する場合, ここで新しい要素として Bucket を生成する. */
p = (Bucket *) pemalloc_rel(sizeof(Bucket), ht->persistent);
if (!p) {
return FAILURE;
}
p->arKey = NULL;
p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */
/* Bucket は自分が何番目の要素であるかを知っている. */
p->h = h;
INIT_DATA(ht, p, pData, nDataSize);
if (pDest) {
*pDest = p->pData;
}
/* 双方向リストを更新する. */
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
HANDLE_BLOCK_INTERRUPTIONS();
/* HashTable 内の配列に新たに生成した Bucket を追加. */
ht->arBuckets[nIndex] = p;
CONNECT_TO_GLOBAL_DLLIST(p, ht);
HANDLE_UNBLOCK_INTERRUPTIONS();
/* 次の空き要素として, 今追加した要素の次を指定する. */
if ((long)h >= (long)ht->nNextFreeElement) {
ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX;
}
/* 要素を新規に挿入したので, HashTable の要素数を同期する. */
ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht);
return SUCCESS;
}
HashTable の arBuckets という配列に, 新しく生成した値をセットしつつ, 双方向リストとしても整合を取るよう, CONNECT_TO_BUCKET_DLLIST マクロを呼んでいる.
CONNECT_TO_BUCKET_DLLIST は ./Zend/zend_hash.c で定義されている.
#define CONNECT_TO_BUCKET_DLLIST(element, list_head) \
(element)->pNext = (list_head); \
(element)->pLast = NULL; \
if ((element)->pNext) { \
(element)->pNext->pLast = (element); \
}
異常から大体以下のようなことがわかった.
次は Bucket 構造体についてでも読んでみようと思う.
前回は PHP の zval 構造体についてまとめた.
ここからは, zval 構造体の value メンバに配列として格納される HashTable という構造体について調べる.
今回の記事でも GitHub における php/php-src の commit c5d10ddda394b573dfaea1380285e1dd5f3c0d50 を前提としている.
HashTable 構造体は ./Zend/zend_hash.h において以下のように定義されている.
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer; /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
各メンバの名前を観る限りでは, メタデータとしてハッシュテーブルの大きさ, 要素の数などと思われる, 様々な値が格納されているとわかる.
PHP の zval を読む #3 で読んだ php_count_recursive 関数を再び読んでみよう.
これは zval 中の配列から要素数を数える関数で, ./ext/standard/array.c で定義されていた.
static int php_count_recursive(zval *array, long mode TSRMLS_DC)
{
long cnt = 0;
zval **element;
if (Z_TYPE_P(array) == IS_ARRAY) {
if (Z_ARRVAL_P(array)->nApplyCount > 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
return 0;
}
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
// mode == COUNT_RECURSIVE 時の処理は省略
}
return cnt;
}
Z_ARRVAL_P マクロが zval のポインタから value, そしてその中の ht メンバを取り出していることも PHP の zval を読む #3 で読んだ.
この ht メンバが HashTable 構造体だった.
それでは zend_hash_num_elements 関数を読んでみよう.
これは ./Zend/zend_hash.c で定義されている.
ZEND_API int zend_hash_num_elements(const HashTable *ht)
{
IS_CONSISTENT(ht);
return ht->nNumOfElements;
}
何ということもなく, HashTable 構造体のポインタから nNumOfElements メンバを取り出しているだけである.
(IS_CONSISTENT マクロはデバッグ用の処理だろうか)
ということで PHP の配列は要素数を持っており, PHP の count 関数の適用時には再計算は行われていない, ということがわかった.
また, おそらく配列に要素を追加, または削除したときなどは nNumOfElements の値も書き換えられているであろうことが予想される.
次回は配列の操作について調べてみようと思う.
以下の記事において PHP の zval について調べてきた.
これらを一旦まとめる.
個人的なメモを殴り書くスタンスのブログなので間違いも多分に含まれるとは思いますが, ツッコミなどいただければ幸いです.
次回以降は zvalue_value 共用体の中にある ht メンバの HashTable について調べる.
今回の記事でも GitHub における php/php-src の commit c5d10ddda394b573dfaea1380285e1dd5f3c0d50 を前提としている.
前回までで大体以下のようなことがわかってきた.
ここまで zval の型について調べてきたので, ここからは値, すなわち value メンバについて調べる.
なお, GC まわりの話は面倒だと思うので一旦避ける予定である.
今回も ./ext/standard/array.c の php_count_recursive 関数を読む.
PHP の count 関数は本来, 第二引数が COUNT_RECURSIVE なら配列を再帰的に処理するが, こんかいの説明をする上では本質的ではないので, 省略している.
static int php_count_recursive(zval *array, long mode TSRMLS_DC)
{
long cnt = 0;
zval **element;
if (Z_TYPE_P(array) == IS_ARRAY) {
if (Z_ARRVAL_P(array)->nApplyCount > 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
return 0;
}
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
// mode == COUNT_RECURSIVE 時の処理は省略
}
return cnt;
}
PHP で count に渡された第一引数は, ここでは zval array となっている.
例外処理と思われる部分を除けば, 本質的なのはほぼ以下の 1 行だと思われる.
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
この Z_ARRVAL_P を追ってみよう.
以下は ./Zend/zend_operators.h から.
#define Z_ARRVAL_P(zval_p) Z_ARRVAL(*zval_p)
zval_p (元は array) が指す値を Z_ARRVAL に渡し,
#define Z_ARRVAL(zval) (zval).value.ht
Z_ARRVAL では zval の value メンバ, さらにその中の ht というメンバを読み出している.
value メンバにはどんな値が入っていたのだろうか.
改めて zval 構造体の定義を ./Zend/zend.h で確認しよう.
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
value メンバの型は zvalue_value となっている.
そしてこの zvalue_value は同じく ./Zend/zend.h で定義されている.
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
zvalue_value は共用体なので, zval の value メンバには, これらのいずれかが格納されていることになる.
先の Z_ARRVAL では, zval は配列なので, ht メンバから HashTable という型のポインタを取り出していることになる.
次回は一旦 zval についての簡単なまとめとしようと思う.
今回の記事では GitHub における php/php-src の commit c5d10ddda394b573dfaea1380285e1dd5f3c0d50 を前提としている.
前回に引き続き zval 構造体について調べる.
今回は PHP におけるデータ型について調べて行きたい.
./Zend/zend.h を適当に眺めていると, 以下のような定数があった.
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9
#define IS_CALLABLE 10
恐らく PHP の基本的なデータ型に対応していると思われる.
PHP 5.4 なので callable もある.
CONSTANT_ARRAY というのは何だろう.
とりあえずは IS_ARRAY がどのように使われているか調べてみることにする.
git grep するとたくさん出てくるので, 検索範囲を絞ってみることにする.
PHP の標準関数の中で, zval や IS_ARRAY がどのように使われてみるか調べてみよう.
標準関数は ./ext/standard ディレクトリ内で定義されている.
参考: PHPソースコードリーディング入門(とっかかり編) - id:anatooのブログ
./ext/standard/array.c の中を探していると, 以下のような関数が見つかった.
static int php_count_recursive(zval *array, long mode TSRMLS_DC)
{
long cnt = 0;
zval **element;
if (Z_TYPE_P(array) == IS_ARRAY) {
if (Z_ARRVAL_P(array)->nApplyCount > 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
return 0;
}
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
if (mode == COUNT_RECURSIVE) {
HashPosition pos;
for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **) &element, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos)
) {
Z_ARRVAL_P(array)->nApplyCount++;
cnt += php_count_recursive(*element, COUNT_RECURSIVE TSRMLS_CC);
Z_ARRVAL_P(array)->nApplyCount--;
}
}
}
return cnt;
}
これは PHP 標準の count() 関数の内部で呼ばれている関数である.
if (Z_TYPE_P(array) == IS_ARRAY) という記述から, Z_TYPE_P は zval のポインタを渡してその型を調べるためのマクロだと思われる.
Z_TYPE_P は ./Zend/zend_operators.h で定義されていた.
#define Z_TYPE(zval) (zval).type
#define Z_TYPE_P(zval_p) Z_TYPE(*zval_p)
Z_TYPE_P が zval のポインタから型を調べるためのマクロで, その内部では zval の値から型を調べる Z_TYPE マクロが呼ばれている.
そしてこの Z_TYPE マクロは zval の type メンバを読み出してだけである.
というわけで, 前回の記事と総合して, zval の type メンバは PHP におけるデータ型を保持している, ということがわかった.
続く?
今回の記事では GitHub における php/php-src の commit c5d10ddda394b573dfaea1380285e1dd5f3c0d50 を前提としている.
zval というのは PHP のソースコード中で使用される構造体で, PHP 中で使われる値を持つ汎用的な構造体のようだ.
PHP のソースコード中には この zval が多数登場する.
./Zend/zend.h で以下のように定義されている.
typedef struct _zval_struct zval;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
このように, zval は 4 つのメンバから構成されている.
名前から以下のようなものだと想像される. (あくまで想像である点に注意)
次に, type メンバの型である zend_uchar について調べてみる.
git grep などで探してみると, ./Zend/zend_types.h に見つかった.
typedef unsigned char zend_bool;
typedef unsigned char zend_uchar;
typedef unsigned int zend_uint;
typedef unsigned long zend_ulong;
typedef unsigned short zend_ushort;
何のことは無い, ただの unsigned char だった.
その他にもいくつか似たようなものがあり, それぞれそれっぽい型になっているが, zend_bool だけは unsigned char となっている.
次回はもうちょっと PHP のデータ型がどのようになっているか調べよう.
今まで書いて来た Born Too Late の住み分けとしては, あっちがいろいろ調べてからまとめる用, こっちはもっと殴り書いていく感じにしたい.
1 class Foo
2 def bar
3 :baz
4 end
5 end
米女性3人監禁事件の英雄、ハンバーガー「一生無料」に | 世界のこぼれ話 | Reuters[クリーブランド 23日 ロイター] 米オハイオ州の民家で女性3人が約10年監禁されていた事件では、救出に駆け付けた隣人男性のチャールズ・ラムジーさんが一躍ヒーローとなり、地元レストランからはハンバーガー生涯無料の申し出も相次いでいる。 同州クリーブランドで女性3人が発見、無事保護されたのは今月6日。ラムジーさんはその日、隣家からの女性の叫び声を聞き、食べかけの「ビッグマック」を放り出して駆け付け...
taketyan 「*写真を変えて再送します」って何...
GifzoWin 宇宙一簡単なスクリーンキャスト共有ツールのWindows版クライアントをオープンソースにしました。 - hidesysの日記2013-05-23 GifzoWin 宇宙一簡単なスクリーンキャスト共有ツールのWindows版クライアントをオープンソースにしました。 あなたは宇宙一簡単なスクリーンキャスト共有ツール、Gifzoを知っていますか??Gifzo GyazoライクなGIFスクリーンキャスト共有ツール「Gifzo」をリリースしました - 海峡 便利ですね。 Windows版の最新バイナリはこちら。ういうれおくんがW...
GifzoのMacクライアントをオープンソースにしました - 海峡2013-05-23 GifzoのMacクライアントをオープンソースにしました GyazoライクなGIFスクリーンキャスト共有ツール「Gifzo」をリリースしました - 海峡先日、公開したGifzoですが、Macアプリのコードをオープンソースにしました。URLをいじるとアップロードする先のサーバーを変えたりできます。uiureo/Gifzo.app · GitHubよろしくお願いします!!!!!!...
Gifzo - 宇宙一簡単なスクリーンキャスト共有Gifzoでデスクトップ映像をGIFアニメで光速にアップロード Download ( Mac 10.7+, Win 2000+ )
「花だすばら飯」/「毒うさぎサン」のイラスト [pixiv]This illustration, "花だすばら飯", is tagged with"花田煌" "謎の存在感のある角" and others.Did you find anything you liked? Join pixiv now and get connected with other users and artists today!
【閲覧注意】「今ピクシブで「魔法少女まどか☆マギカ」を検索してはいけない」というツイートが出回る その真相は? | ニコニコニュース@henomo20 何となく狂気と言うか怒りとかそういうのを感じたけど…同じ絵の使い回しが多すぎ @fleet622 なんとまあ、記事にまでなるとはねえ。 @yoshidashuttle これかー。同じ人の連投じゃん。 @taishaku10 下手なのはともかく明らかに不快にさせるだけの作品を垂れ流すのはアートとは言わん。ただの迷惑行為だ @taishaku10 pixivは定期的にキチガイが大量...
taketyan これか http://www.pixiv.net/member.php?id=3301850
〈私のコミック履歴書〉ラーメンズ 片桐仁さん - 朝日新聞社広告局 - コミック・ブレーク (広告特集) | BOOK.asahi.com:朝日新聞社の書評サイト■自分の立ち位置 ――片桐さんはたいへんなマンガ好きで知られていますね。 片桐 マンガの話だったら何時間でもできますよ! マンガ評論家になりたいと思ってるくらいですから(笑)。最近は雑誌よりもコミックス派ですけど、20代の頃はヒマだったこともあって、主だったマンガ雑誌をほとんど読んでいました。「(少年)ジャンプ」「ヤンマガ(ヤングマガジン)」「スピリッツ」「(少年)サンデー」「(少年)マガジン」「...
Pentagram SIGN OF THE WOLF Cover (Acoustic)I know I look tired here and I actually WAS! haha :D So I almost fell asleep. Same thing as with the Kadavar-Vid. Found the Video online on WDR Rockpalast and was amazed by the sound. Didn't know a Do...
プログレ聴いたことのない人に薦めるプログレ - 券売機で購入出来ます。2013-05-17 プログレ聴いたことのない人に薦めるプログレ 音楽 ジョジョのアニメのエンディングかっこよかった!ああいうのがプログレなんだとしたら結構興味あるんだけど!! っていう人のために書きます。まだ狂気すら聴いてない人向きです。 ちょっとは聴いたけどこの次どこ聴けばいいの?って人向きには別に記事を書きたいと思います。 プログレっていうのはプログレッシヴ・ロックの略です。 1960年代後...
Pentagram の Sign of the Wolf (Pentagram) 弾いてみた ‐ ニコニコ動画:Q弾いてみたは初投稿です。Black Sabbath と並び最初期から現在まで、ドゥームメタル・ストーナーロックに多大な影響を残してきた Pentagram のセルフタイトル曲とも言える名曲です。
ツアーの軌跡を綴った初のデジタルパンフレットアプリ!「吉井和哉 TOUR 2013 AFTER PAMPHLET」ツアー終了直後にリリース!! - ValuePress! [ プレスリリース 配信サイト ]ツアーの軌跡を綴った初のデジタルパンフレットアプリ!「吉井和哉 TOUR 2013 AFTER PAMPHLET」ツアー終了直後にリリース!! EMTG 株式会社は、吉井和哉の全国ツアー「TOUR 2013 GOOD BY YOSHII KAZUYA」の最終公演の終了に合わせ、本ツアーの軌跡を綴ったデジタルツアーパンフレットアプリ『吉井和哉 TOUR 2013 AFTER PAMPHLET』を5月...
Last Evolution (Utena OST1)言語設定を日本語に変更しました。この設定は各ページ下部で変更できます。 We've set your language preference to Japanese. You can update this preference below.
taketyan 最後のとこがめっちゃカッコいい
スズメバチの巣で自慰行為、刺されて死亡 - 国際ニュース : nikkansports.comスウェーデンの35歳男性が、スズメバチの巣で自慰行為をした後、スズメバチに全身を刺されて死亡した。地元紙の報道によると、同国南端の都市イースタッドで13日昼、35歳男性が自身所有の牧場で倒れ、発見後約1時間で亡くなった。 警察の調べによると、遺体の近くにスズメバチの巣があり、巣の中に男性の陰毛が残っていた。さらに死んだスズメバチの上に男性の精液がかかっていたことから、男性は自分のペニスを巣に挿入し...
taketyan 発見者もほっといてやれよと思う
「検索してはいけない」で有名な、あの愛生会病院HPが閉鎖へ | ICT Headline directed by P検インターネット上で「検索してはいけないワード」として有名な、愛生会病院のホームページが閉鎖されることがわかった。 愛生会病院のホームページは、少なくとも1998年ごろには開設されていたと見られている。病院のホームページというイメージとは真逆の特徴的なカラーリングなどから、衝撃的だとして「愛生会病院」という言葉を「ネットでは(ショックが大きいので)検索してはいけない」と揶揄されるまでになった。実際、...
taketyan とても残念
東京地裁:反町、松嶋夫妻のドーベルマン、佐藤可士和さん妻にかみつく 385万円賠償命令- 毎日jp(毎日新聞)俳優の反町隆史さん、松嶋菜々子さん夫妻の飼い犬が同じマンションの住人にかみつき、負傷した住人が転居したため賃料収入を失ったとして、東京都目黒区のマンション管理会社が反町さん夫妻側に約5220万円の支払いを求めた訴訟の判決で、東京地裁(宮坂昌利裁判長)は14日、夫妻に385万円の支払いを命じた。 判決によると、夫妻は渋谷区内のマンションに居住。飼っていたドーベルマンが2011年5月、3階フロアで同じ...
taketyan 当事者には悪いけど出演者が豪華過ぎておもしろい
JavaScriptのプログラミングスタイルはどうあるべきか? 重鎮Douglas Crockford氏が脳の働きとの関係を語る(前編)。QCon Tokyo 2013 - PublickeyJavaScriptのプログラミングスタイルはどうあるべきか? 重鎮Douglas Crockford氏が脳の働きとの関係を語る(前編)。QCon Tokyo 2013 4月23日に都内で開催されたエンジニア向けのイベント「QCon Tokyo 2013」。基調講演に登壇したのは、JavaScriptの重鎮であるDouglas Crockford氏。「プログラミング・スタイルと私たちの脳」という大...
taketyan いつの間に PayPal に...
ばあちゃるぷろだくつ サイレント木魚「寂尊」HOME サイレント木魚「寂尊(じゃくそん)」 ヘッドフォンでこっそり練習、アンプにつないで大音響でプレイ。自由自在に楽しめるデジタル木魚です。 希望小売価格 (税込) SMJ-201 (ベーシックモデル) ¥59,800 SMJ-201M(MIDIモデル) ¥75,800 サイレント木魚「寂尊」の特長 特殊消音パッドセンサの採用により、直接打音はほとんど聞こえません。 ヘッドフォンまたはアンプ...
DI コンテナがコードに出現するのは dependency lookup が行われている兆候 - TogetterDependency Injection (DI) コンテナを基盤にしているアーキテクチャを採用している場合に、(プロダクト/テスト)コードに DI コンテナが登場するのは dependency lookup を行っている兆候と考えることが出来ます(もちろん必要があってコンテナの injection が行われている場合もあります)。 テストに DI コンテナが出現することに気付いたら、 Depen...
taketyan DI コンテナへの依存はアプリケーションレイヤに閉じろ、という話なのかな
夜明けのスキャット 由紀さおり ~AutumnSnake夜明けのスキャット 由紀さおり ~AutumnSnake 公式ブログ: http://blogs.yahoo.co.jp/autumn_snake_1995
なんじゃこりゃああああああああああ Googleストリートビューに新種の二足歩行猫が映り込んでネット騒然 - ねとらぼ http://b… |ねとらぼさんのついっぷるトレンド画像なんじゃこりゃああああああああああ Googleストリートビューに新種の二足歩行猫が映り込んでネット騒然 - ねとらぼ http://bit.ly/10i2oPs @itm_nlabさんから
taketyan ヤバい
フルタイム労働だけがまともだと思われる世界だるい
日本に留学に来た学生たちは、ほぼ例外なく『ナルト』や『ワンピース』、ジブリ作品などから日本を知る。間違っても阪神タイガースや浦和レッズではない。
自尊心の低い人ほど「日本人であること」に固執する。なぜなら「日本人はすばらしい」という価値観に染まっていれば、なんの努力もせずに「自分はすばらしい」と思えるからだ。
女性の幹部登用
日本アイ・ビー・エムの社長当時、女性の幹部登用に積極的であった。同社の幹部候補を選出する際には、大歳は毎回必ず「女性の候補者はいるのか」と繰り返し質問していたという[1]。インタビューなどで「幹部候補の見直しの時に女性を必ず何割は入れるように、と言い続けて10年になります」[2]と発言している。女性に対する撮影
2012年8月 JR四ッ谷駅構内で女性のスカート内を盗撮し、東京都迷惑防止条例違反容疑で事情聴取されていたことが明らかになった
自分はクライアントサイドの人間だ。望めばどんなコードで何が動いているのか検証できる世界、望めば拒否できる、望めば完全にブロックできる、そういう世界を望むだろう。コードを読まず、あるいは読めずに憶測で批判する人間が大半であれば、あるいは少数でも異常に声が大きい人がそういう事をやってしまえば、実現しない。
11 アメリカンショートヘア(チベット自治区) :2012/08/30(木) 12:39:21.85 ID:CwvlYajX0
I いいじゃん
B 別に
M 股くらい
真面目系クズの特徴 ・一見真面目に見えるが成績が伴わない ・外面はいいので上司や教師からの評価は高い ・やればできると思ってるがやらない ・レポートの提出はギリギリ ・人が見てるとこでは頑張るが、見ていないとこでは全力でサボる ・知人は多いが友達は少ない ・あ、はい
276 : 風吹けば名無し : 2012/08/29(水) 00:53:44.99 ID:deMcUvbh
野球未経験者の妹(18)をバッティングセンターに連れて行ったとき、初球のストレート(100km/h)が死球で病院送りになったわ。
間接の事故原因は、俺が一瞬目を離していたこと。
直接の事故原因は、ホームベースの上に立ってバットを構えていたこと。
信じられんだろうが実話だ
あなたを深く知るひとはあなたを深く愛するひととは限りません。同時に、深い愛をくれるひとはあなたを知らなくとも愛をくれます。愛の証を欲するがあまり、相手が自分をどれだけ知っているのかテストしてはなりません。
それは愛ではなくただの認知です。
- 当時の俺が国会中継見たとき、たしかこんなだった
カップ麺問題
民主「総理!カップ麺の値段をご存知ですか?」
麻生「いや、これ国防についての会議なんだけど」
民主「カップ麺の値段を答えてください!」
麻生「……400円くらいじゃないの?で、国防についての質問は?」
民主「ありません。以上です」
消費税増税問題
麻生「数年後、景気回復したら消費税をあげたい」
民主「不況なのに消費税を上げるなんてとんでもない!」
麻生「だから、景気が回復したらって言ったじゃん。回復しなきゃあげないよ」
民主「増税しないの!?増税しろよ!うそつき!」
郵政民営化見直し問題
麻生「見直しの時期になったから改善点について話し合おうか」
民主「郵政民営やめるの!?あんた小泉政権の時に賛成してたのに!?」
麻生「いや、この時期になったら見直すって条件だから。それに民営化やめるなんて言ってないし」
民主「今言ったじゃん」
麻生「見直すのとやめるのは意味が違うでしょ」
民主「一緒だよ!漢字も読めないくせに!」
どう考えても民主が狂ってるのに、報道ではみんな「麻生が悪い!政権交代!」に- なっててマスコミ不信になった。