PHPを高速で実行できるHHVMですが、プログラムをhackで記述する事で更なる恩恵を得る事ができます。
その中でも特に画期的なのが async / await を使った非同期処理の実現です。
公式のマニュアルから抜粋してその利点をまとめます。
Asyncとは
Hackはasync
という協調的マルチタスキングをプログラムから利用できる機能を提供します。これによりコードからasync機構を使うことで入出力(I/O)レイテンシやデータの取得処理を隠蔽できます。コード中でネットワークアクセスやデータベースのクエリを待機する処理がある場合、async
は入出力に関わらない他の処理を行う事で、待機時間を最小化します。
async
はマルチスレッドではありません。HHVMは PHP/Hack のコードを一つのメインリクエストスレッドで実行します。しかしMySQLのクエリのようなその他の処理はコードの実行時間を使わずに実行できるのです。
制限
- 全ての PHP/HAck コードはメインリクエストスレッドで実行される。
- ブロッキングなAPI (例 mysql_query(), sleep()) は自動的に非同期な処理に変換されません。 これはそれぞれの実行順序が変わる事によって予期しない影響が起こることが安全ではない為です。
Asyncの例:cURL
asyncを使わないで2つのcURLリクエストを行うと次のようになります。
<?hh
namespace Hack\UserDocumentation\Async\Intro\Examples\NonAsyncCurl;
function curl_A(): mixed {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.com/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
return curl_exec($ch);
}
function curl_B(): mixed {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.net/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
return curl_exec($ch);
}
function main(): void {
$start = microtime(true);
$a = curl_A();
$b = curl_B();
$end = microtime(true);
echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}
main();
出力
Total time taken: 1.050155878067 seconds
上記の例ではcurl_A()
の中のcurl_exec()
は全ての処理をブロックしています。その結果、curl_B()
がcurl_A()
と独立した処理であってもcurl_A()
の処理が終わるまで待ってから実行されます。
幸い、HHVMは非同期版のcurl_exec()
を提供しています。
<?hh
namespace Hack\UserDocumentation\Async\Intro\Examples\Curl;
async function curl_A(): Awaitable<string> {
$x = await \HH\Asio\curl_exec("http://example.com/");
return $x;
}
async function curl_B(): Awaitable<string> {
$y = await \HH\Asio\curl_exec("http://example.net/");
return $y;
}
async function async_curl(): Awaitable<void> {
$start = microtime(true);
list($a, $b) = await \HH\Asio\v(array(curl_A(), curl_B()));
$end = microtime(true);
echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}
\HH\Asio\join(async_curl());
出力
Total time taken: 0.74790596961975 seconds
非同期版のcurl_exec()
はcURLのレスポンスを待っている間にスケジューラーに他のコードの実行を許します。この場合は非同期的にcurl_B()
をさらに実行します。スケジューラーが処理を実行するともう一つの非同期なcurl_exec()
が実行されます。HTTPリクエストは演算処理よりは遅い為、メインスレッドはリクエストが終わるまでアイドルします。
実行順序は大体、上記のようになりますが、そうでない場合もあります。例えば、curl_B()
のリクエストがcurl_A()
のHTTPより大幅に速い場合はcurl_B()
の処理がcurl_A()
の処理が完了する前に完了します。
非同期処理による速度向上の恩恵はネットワークの状態はDNSのキャッシュなどにより変動的です。ですが劇的な効果になりえます。
まとめ
HHVMを使ってコードを実行する場合、入出力に関する部分だけでもhackを使って書き換える事で非同期処理を実現する事ができます。リモートのWEBAPIコールやデータベースの処理を順次実行するようなアプリケーションの場合は大幅な高速化を実現できる可能性があります。
非同期処理をPHPで実現する為に複雑なバックエンドを構築する必要がある場合は、HHVMの備える非同期処理の利用も検討してみるとよいのではないでしょうか。
また非同期処理を実現する際には\HH\Asio
配下にあるコレクションの処理や関数を確認しておくと良いでしょう。
- \HH\Asio\m
- 非同期対応の拡張 mysql, memcache, curl, stream