hnwの日記

PHPのsleep関数とusleep関数の挙動を調べてみた

筆者はPHPの現在時刻を上書きするPHP拡張モジュールphp-timecopを開発しているため、PHPの時間がらみのテストを世間一般の人より多く書いていると思います。テストケース中でusleep関数を多用しているのは世界中でも筆者くらいかもしれません。

ところで、先日php-timecopのテストをWindows上で動かしたところ、 usleep(100000) が99.8msくらいで帰ってきてテストに失敗するということがありました。

筆者はsleep関数やusleep関数は指定した時間と同じかそれより長い時間スリープすると考えていたのですが、本当にそのような性質があるのでしょうか?また、sleep関数やusleep関数はどの程度の誤差があるのでしょうか?

本稿ではこうしたsleepやusleepの挙動について深掘りしてみます。

sleep関数の挙動

まずはsleep関数の挙動から調べてみましょう。LinuxmacOSWindowsの各環境で sleep(1) して帰ってくるまでの時間を1000回測定したときの結果を下記に示します。

Linuxの場合

下図がLinux環境(Debian 8.7, Kernel 3.16.0, x86_64, PHP 5.6.30)での実験結果のヒストグラムです。横軸の単位はusです。

1.0001秒あたりにピークがあるのがわかります。言い換えると約100usのズレです。

Windowsの場合

Windows環境(Windows 10, x86_64, PHP 7.1.6)での実験結果のヒストグラムです。

ピークは1.001秒あたりになっています。言い換えると約1msのズレです。

macOSの場合

macOS環境(Mac OS X 10.11.6, x86_64, PHP 7.1.6)での実験結果のヒストグラムです。

ピークは1.005秒あたりになっています。言い換えると約5msのズレです。

3環境とも、1秒より早く帰ってきたものは1件もありませんでした。

POSIXの仕様を確認する

ちなみに、PHPのsleep関数はどの環境でもOS/標準ライブラリのsleepを呼び出しています。POSIXのsleep()のmanpageには次のような記述があります。

The sleep() function shall cause the calling thread to be suspended from execution until either the number of realtime seconds specified by the argument seconds has elapsed or a signal is delivered to the calling thread and its action is to invoke a signal-catching function or to terminate the process. The suspension time may be longer than requested due to the scheduling of other activity by the system.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html

指定された時間が経過するまで実行を停止するよ、スケジューリングの都合で指定されたよりも長く停止することがあるよ、とのことですから、指定時間より早く帰ってくることはないことが保証されているわけです。

usleep関数の挙動

次にusleepの傾向を調べてみます。sleep関数と同様に3環境でusleep(1000000)して帰ってくるまでの時間を測定しました。

Linux/macOSの場合

LinuxmacOSの場合はsleep(1)のときと大差ない結果になりました。下記のヒストグラムLinuxの結果です。

以下はmacOSの結果です。

どちらの環境もsleep関数のときと同様、1秒より早く帰ってくるものは1件もありませんでした。両OSとも内部的にPOSIX準拠のusleepを呼び出しているため、sleep関数のときと似た挙動なのは当然と言えそうです。

Windowsの場合

さて、Windowsの場合は意外とも言える結果になりました。

コブが2つある分布になっており、最初のピークは1000000ns(=1秒)より僅かに前になっています。本稿の最初でWindows上のPHPではusleep関数が指定した時間より少し早く帰ってくることがあるという話を紹介しましたが、その通りの結果になっているわけです。

PHPWindows用のソースコードを見てみると、usleep関数の実現にはWindowsの「Waitable Timer Objects」が利用されています。また、これを利用すると停止時間が指定より短くなることがあるようです(たとえば下記の記事を参照)。

まとめ

  • PHPのusleep関数はWindows上では指定された時間より短い停止時間になることがある
    • Windows APIを使った独自実装をしているため
    • 手元の環境では最大1ms程度早まった(仮想環境ではさらにズレる印象)
  • 全ての環境のsleep関数・Linux/macOS上のusleep関数は指定された時間と同じかそれ以上停止する
    • 内部的に呼び出しているライブラリコールsleep・usleepの仕様
    • 指定された秒数からどれくらいズレるかはOSによって異なる

usleepという関数名なのにWindows上の挙動が他環境と異なっているのはバグとまでは言い切れませんが、ポータビリティ上は問題がありそうです。念のためPHP本体にバグレポートを出しておこうと思います。

追試用の情報

今回の実験に使ったPHPスクリプトは下記です。カーネルコンパイルオプションやその他の条件次第で結果が変わるかと思います。

php-timecopをPECLに登録しました

かれこれ5年ほどメンテしている拙作のPHP拡張「php-timecop」ですが、このたびPECLに登録しました(PECL :: Package :: timecop)。


PECLというのはPHP本体に含まれないPHP拡張を提供する公式のリポジトリです。PECLのアカウントは承認制になっており、誰でも登録できるわけではありません。イタズラやお試しでの登録は減るでしょうが、代わりに登録への精神的ハードルが上がってしまうような仕組みだと言えるでしょう。実際、PECLに登録されているパッケージ総数は365個(2017/7/8時点)と多くはありません。また、日本人と思われるPECLアカウントは筆者以外では5人でした。


本稿では、PHP拡張をPECLに登録するまでのプロセスや、実際に登録してみてわかったことなどを紹介します。

PECLに登録するメリット

さて、そのPECLですが、PEAR*1の衰退とともに徐々に存在感が薄れている印象があります。今さらPECLに登録するメリットとは何でしょうか?


今回PECLに登録してみて、メリットとして次の3点を感じました。

  • peclコマンドを利用している人はインストールが簡単になる
  • PECLに登録することでコード品質が高そうに見える
  • PHP拡張のWindowsビルドが勝手に行われる


1番目ですが、peclコマンドで拡張をインストールしている人が一定数いるようです。そういう人にとっては、下記のコマンドでPHP拡張がインストールできるのは大きなメリットでしょう。

$ pecl install timecop-beta # 正式リリース後には「-beta」が不要になります


私個人はphpizeでビルドするのが普通になってしまったのでpeclで扱えてもありがたみは感じないのですが、インストールまでの手間は確実に減るので、悪いことではないでしょう。


2番目は印象論に近い話になりますが、PECLに登録してあるパッケージの方がロングサポートが期待できたり、多くの人のチェックが入っているように見えるかと思います。実際、後述するようにアカウント取得時にレビューが入りますので、PECLに登録されている時点で一定以上の品質だと期待できるでしょう。また、PECLのサイトに掲載されることで宣伝になるような側面もあるはずです。


最後はあまり知られていない気がしますが、PECLにはWindows版DLLの自動ビルドの仕組みがあります。PECLにパッケージをリリースすると、数時間後にWindows版DLLが勝手にアップロードされます。しかも、5.5から7.1までの4バージョン、スレッドセーフ有効/無効、32bit版/64bit版の全組み合わせ16個のDLLが提供されます。自力でここまで対応するのは大変なので助かりますね。

PECLアカウント取得まで


PECLアカウントの取得は承認制だと書きましたが、割とゆるい感じで運用されています。手順は全て「PECL :: Request Account」に書いてあるのですが、改めて紹介します。

  1. メーリングリスト pecl-dev@lists.php に参加して「自己紹介」「PECLに登録したい拡張の紹介」「コードへのリンク」を書き込む
  2. 誰かがレビューしてくれるので、返事をしたりコードを修正したりする
  3. 頃合いを見て「PECL :: Request Account」のフォームを埋める


1番目はそのままです。私は自己紹介が若干適当でしたが、仕事でPHPに触るようになって何年、とか言っておけばいいんじゃないでしょうか。


2番目については、割とすぐに誰かがコードを見た上で返事をくれます。人によっては「拡張の中でfork()するのは頂けない」みたいな真面目なコメントがついたりします。私の場合はRemiさん*2がコードレビュー&動作確認をしてくれて、「PHP 7.2で動かないよ」「テストが何件か通らないよ」という指摘をもらいました。


MLでの議論を尽くしたらアカウント申請を行います。リクエストフォームの「Sponsoring users」の欄にML上でレビューしてくれた人の名前を書けば良いでしょう。ここで登録したメールアドレスなどの情報はアカウントが作成された後のプロフィールページの初期値になります(参考:「PECL :: Yoshio HANAWA」)。


ちなみに、PECLアカウント申請だけではphp.netアカウントは作成されません。必要なら別途申請する必要があります。

PECLパッケージを作るまで

PECLにアップロードするパッケージは適当なtar ballではダメで、peclコマンドが取り扱える形式でないといけません。この作成にはpeclコマンドが必要です。もし手元に見当たらない場合はpearをインストールする必要があります。


また、パッケージングには package.xml が必要です。既存のパッケージを参考に適当に書いた上で、同じディレクトリで下記のようにすればパッケージが作られます。

$ pecl package
Package timecop-1.2.8.tgz done


XMLの中身がおかしいと警告やエラーが出たりしますので、適宜修正してください。できたtgzファイルをPECLのWeb管理画面からアップロードすればリリース完了です。CIと連携するようなオシャレなAPIは無いみたいです。


既に紹介した通りPECLパッケージのリリースのたびにWindowsのビルドが走るのですが、ビルドに失敗するとDLLがアップロードされません。ビルド時のログが下記のようなURLから確認できるので、失敗していた場合はログから原因を推測する必要があります。Windowsビルドを通すために何度もリリースするのも格好悪いので、真面目にやるなら手元にビルド環境を用意した方がいいでしょう。

まとめ

そんなわけで無事PECLへのリリースができました。3年前にchobieさんに言われたときからの宿題がようやく終わって良かったなーと思っています。



手元にPHP拡張を隠し持っているみなさんもPECLにリリースしてみてはいかがでしょうか。

*1:PHPライブラリのパッケージマネージャ&リポジトリ。現在はComposerが主流

*2:Remi's RPM repositoryの運営者、かつPHPコアコミッターの一人

古いPHPでDateTime::modify(’+0 days’) すると時間がずれるバグ

表題の通りですが、PHPの特定のバージョンにおいて、生成したDateTimeオブジェクトに対して時刻の操作を行うと期待と1時間ずれてしまうことがあります。

<?php
$dt = date_create('@0');
var_dump($dt->format('c')); // string(25) "1970-01-01T00:00:00+00:00"
$dt->modify('+0 days');
var_dump($dt->format('c')); // string(25) "1970-01-01T01:00:00+00:00" (PHP 5.3.9 - 5.4.7)


上記コードの場合で言えば、Unix epoch(Unix time 0秒)のDateTimeオブジェクトを生成してmodifyメソッドで0日後を指定すると、なぜかDateTimeオブジェクトの指す時刻が1時間後になってしまうのです。


このバグの再現には、次の条件が揃う必要があります。

  • PHP 5.3.9 - 5.3.29 または 5.4.0 - 5.4.7
  • 実行環境の現在時刻が夏時間
  • DateTimeオブジェクトをUnix time指定で作成している("@12345"のように)
  • 作成したDateTimeオブジェクトに対してmodifyメソッドやsetTimestampメソッドで時刻操作する


バグの再現に夏時間が必須となると日本人である我々には関係ないようにも見えますが、たとえばTravis CI上のPHP 5.3上でテストを実行すると、今の時期は上記4条件のうち最初の2つを満たしてしまいます。実際に私はハマりました。


このバグは「PHP :: Bug #62896 :: "DateTime->modify('+0 days')" Modifies DateTime Object」で修正されており、PHP 5.4.8以降ではこのような不思議な現象は起こりません。

BuffaloのUSB無線LANアダプタの返す製造元の文字列を解読する

私の手元に「Buffalo WLI-UC-AG300N」というUSB無線LANアダプタがあるのですが、Macの「システム情報」で見ると製造元が「敇瑭步挮浯䩟」となっていることに気づきました。

バッファローとかメルコとか書いてあるなら分かりますが、少なくとも日本語ではありませんし、簡体字なり繁体字なりだとしても不自然に思えます。となると、一体何が表示されているのでしょうか?文字コード警察的な意味で興味を持ったので、調べてみました。

謎解き(1) 何が書いてあるのか

この記事の執筆時点では「敇瑭步挮浯䩟」でGoogle検索しても同じ無線LANアダプタの情報が1件見つかるだけで、そんなメーカーは地球上に存在しなさそうなことがわかります。

ネット上にも情報が無いときに頼れるのは自分の直感だけです。そこで、私は謎の漢字列をUTF-16にしてみることにしました。

上記PHPファイルをUTF-8で保存して実行すると、次の文字列を得ます。

Gemtek.com_J

なんと7bitの可読文字が現れました。UTF-16は原則2バイトで1文字を表しますが、UTF-16の6文字を無作為抽出した場合に対応する12バイト全てがASCIIの7bit可読文字になる確率は0.0007%以下です。これは偶然なわけがありません。

この文字列のGemtekというのは台湾のメーカー「Gemtek Technology Co., Ltd」のことでしょう。同社はバッファロー社の無線LAN製品のOEM仕入れ先としても有名です。

実際、下記URLに「Buffalo WLI-UC-AG300N」のOEM元がGemtek社であることが書かれています。

そんなわけで、隠された文字列は「Gemtek.com_J」であることがわかりました。

謎解き(2) なぜ文字化けが起きたのか

さて、謎の漢字列はGemtek社を表すようですが、どうすればこんな不思議な文字化けが起きるのでしょうか?

実は、下記引用部の通り、USB 2.0の文字エンコーディングはUTF-16LEだと決められています。

Unicode ECN: Released in February 2005.
This ECN specifies that strings are encoded using UTF-16LE. USB 2.0 specified Unicode, but did not specify the encoding.

https://en.wikipedia.org/wiki/USB

つまり、USBで「abc」という文字列を表したい場合、「0x61 0x00 0x62 0x00 0x63 0x00」の6バイトにする必要があるわけです。これを知らずに(もしくは後で作業しようと思って忘れて)実装してしまうと、UTF-16としては不思議な文字列になるわけです。これが今回の文字化けの真相でしょう。

まとめ

WLI-UC-AG300Nの製造元文字列「敇瑭步挮浯䩟」について調べました。

  • 「敇瑭步挮浯䩟」は本来「Gemtek.com_J」となるはずだった
    • これは台湾のメーカーGemtek社を意味する
  • USBの仕様上UTF-16LEで記述すべきところをASCIIで記述したため文字化けが起きた

イースターエッグ的なものである可能性もゼロではありませんが、受託ビジネスでそんな無駄なリスクは取らないと思います。

PHPの連想配列は常にin_arrayより速いのか

プログラムを書いていると、入力値が辞書に含まれているかを調べたいようなことがあります。たとえば、ユーザーに都道府県名を入力させて、それが正しい都道府県名であるかどうかを調べたい、というようなことがあるかもしれません。

このような内容をPHPで書く際、キーに都道府県名を持つような連想配列を作る習慣がある人は多いはずです。これは典型的な連想配列の使い方といえるでしょう。

<?php
$prefs = array(
    "北海道" => true,
    "青森" => true,
    // ...
    "沖縄" => true,
);

if (isset($prefs[$input])) {
    // 都道府県名が正しい時の処理
}

一方で、in_array関数を使うやり方も考えられます。

<?php
$prefs = array(
    "北海道",
    "青森",
    // ...
    "沖縄",
);

if (in_array($input, $prefs)) {
    // 都道府県名が正しい時の処理
}


突然ですが、クイズです。この2つのうち、どちらの方が良いコードでしょうか?

連想配列から特定のキーを持つ要素にアクセスするコストはO(1)であるのに対し、配列から特定の要素を探すコストはO(N)ですから、一般論としては連想配列を使う方が好ましいと言えるでしょう*1。しかし、計算量の議論はNが大きい場合には正しいですが、Nが小さい範囲では係数や定数項など他の要素の影響も無視できないはずです。常に前者の方が良いコードであると言えるだけの材料を我々は持っているのでしょうか。

そこで、今回PHP 5.6.30およびPHP 7.1.5を用いて、配列サイズとアクセス回数を変えながら両者の速度を測定してみました。本稿ではその結果を紹介します。

PHP 5系では連想配列アクセスの方が速い


今回、配列と連想配列とで、辞書データの構築時間と検索時間の和を比較してみました。というのも、一定規模の配列になると構築にかかる時間も無視できないため、たとえ検索が遅くても構築が速い方がトータルでは有利になる可能性があるからです。

早速ですが、PHP 5.6.30での実験結果のグラフを示します。両対数グラフなので見方に注意してください。

配列サイズというのは辞書に含まれる単語数です。また、アクセス回数というのはその辞書に対する検索の回数です。上記グラフではアクセス回数を固定して配列サイズを倍々で増やしていく、ということをしました。

これを見ると、アクセス回数が8回ある状況であれば連想配列の方が1.2から1.5倍程度は速いと言えそうです。また、アクセス回数128回になると差が絶望的に開きます。検索回数が多い場合は(当然ですが)検索の計算量の差が支配的であることがわかります。

連想配列の方がデータの構築に時間がかかるためアクセス回数2回では互角程度になりますが、互角であれば連想配列を使った方が事故は少なそうです。

PHP 7系では配列アクセスに逆転されることがある

面白いことに、PHP 7系だと状況が変わってきます。PHP 7.1.5, アクセス回数8回のグラフを見てみましょう。

配列サイズが小さいうちは連想配列有利だったのが、配列サイズ2048あたりで配列が逆転し、サイズ65536あたりで連想配列が再逆転する、というような結果になっています。何が起こったのでしょうか。

配列サイズが小さいうちは配列と連想配列とで構築コストに大きな差はありません。この段階で差になってくるのは、in_array($key, $array)が関数呼び出しであるのに対し、isset($array[$key])はopcodeにコンパイルされて関数呼び出しにならないということです。PHPの関数呼び出しは他のopcode実行に比べるとコストが高いので、この差で連想配列の方が速くなっているのです。

配列サイズが中程度になってくると、配列と連想配列の構築コストの差が支配的になってきます。PHP 7からは真の配列と言うべきデータ構造が導入されており*2連想配列と比べて簡潔なデータ構造で済むようになりました。このため、同じサイズであれば連想配列より配列の方が構築コストが低く、PHP 5のときは見られなかった逆転現象が起こるというわけです。

さらに配列サイズが大きくなってくるとO(N)とO(1)というアクセス時の計算量の差が効いてくるので、ふたたび連想配列の方が有利になります。

このように、アクセス回数8付近では複雑な状況が見られるわけです。一方、アクセス回数32であれば基本的には連想配列の方が有利となります。

また、アクセス回数8192回では絶望的な差がついてきます。

このように、アクセス回数が大きくなってくると予想通り連想配列の方が有利だということがわかります。

一方、アクセス回数が少ない場合に限れば配列を採用する可能性もゼロではないと言えそうです。とはいえ、プログラムの改修などでアクセス回数が増える可能性を考えると基本的には連想配列を採用しておく方が無難でしょう。

この文章をどう読むかについての補足(2017/5/25追記)

一般論として、普段のコーディングにおいてマイクロベンチの結果を参考にしすぎるのは良い習慣ではありません。たとえば、echoとprintfとでどちらが速い、みたいな内容に引っ張られて書くコードを変える必要はないでしょう。もしそれがボトルネックになっていることが判明したら、判明した後で変えれば良いはずです。

その一方で、マイクロベンチの速度差がなぜ生まれたのか、その原因を調べることで何らかの新しい知見を得ることもあるはずです。本稿で一番お伝えしたかったのは、その面白さについてでした。

加えて、今回は計算量の話題やオーダーの違いの怖さ、といった部分も盛り込んだわけですが、このあたりは人によって感覚が異なるのかもしれません。私個人の意見としては計算量の観点で不利な実装を採用する際は常に理由が必要だと思っており*3、本稿もそのような前提で書かれていますが、異なる意見があってもおかしくは無いと思います。

まとめ

PHP連想配列と配列のin_array()とで、どちらが有利かを検討してみました。

  • PHP 5.6では連想配列が有利
  • PHP 7では「真の配列」のおかげで配列が有利な状況もありうる
    • アクセス回数小、配列サイズ中程度の場合に限る
    • 配列の方が検索コストは高いが、構築コストが低いため
  • 現実には配列が正解という状況は少ないはず
    • アクセス回数と配列サイズの両方が極端に少ない場合は配列の方が読みやすいかも

連想配列の方が有利だというのは特に驚きのない結論ですが、PHP 7の「真の配列」による性能向上が垣間見えたのは面白いと感じました。

ネイティブコードにコンパイルするような言語であれば、辞書サイズが小さいときは連想配列よりも配列が正解という状況もありうると思うんですが、PHPの場合はin_array()が関数呼び出しであることのペナルティが大きく、そのような状況は確認できませんでした。

追試用資料

筆者の手元のマシンはCore M採用のノートマシンであり、Turbo Boostのせいで結果が安定しなかったりするので、結果の信用性は保証できません。追試したいと思われた方は下記URLをご確認ください。

*1:たとえば入力値が辞書に存在しないことを示す場合に、配列だと全要素とN回の比較が発生しますが、連想配列だとハッシュ値が一致する要素(平均では1要素)と比較すれば良いので、計算の効率が格段に違うことになります

*2:参考資料:「PHP7で変わること ——言語仕様とエンジンの改善ポイント

*3:逆にechoとprintfはオーダーが同じなのでどちらでも良いと思っています

PHPのシンボルテーブルを覗いてみる

PHPのシンボルテーブルというのはC実装のレベルの用語で、その時点で有効な変数テーブルのことを指します。つまり、グローバルスコープならグローバル変数を管理する変数テーブル、関数スコープならローカル変数を管理する変数テーブルの意味になります。


今回、自作エクステンションhnw/php-arraydumperでシンボルテーブルをvar_dump()するだけの関数symbol_table_dump()を実装してみました。下記のように使います。

<?php
function foo($a = 1) {
    $b = 2
    symbol_table_dump();
}
foo("bar");


出力は次のようになります。

array(2) {
 ["a"]=>
 string(3) "bar"
 ["b"]=>
 int(2)
}


このシンボルテーブルは$$varのようなイカれた変数アクセスの際に参照する必要がありますが、そうでも無い限り不要です。通常のローカル変数はコンパイル時に全部解決できてしまうので、通常の変数アクセスのみであれば実行時にシンボルテーブルへのアクセスは発生しないのです。


実際、PHP 5.3以降ではローカル変数に対応するシンボルテーブルは必要になるまで作られません。$$varのような変数アクセスがあったタイミングで内部的にzend_rebuild_symbol_table()が呼び出され、そのスコープに対応するシンボルテーブルが構築されます。


逆に言うと、$$varのような変数アクセスを多用するのは性能面でペナルティがあると言えるかもしれません。もちろん致命的な性能差が出るほどではないでしょうし、そもそもそんな変数アクセスをしたい状況が珍しいとは思いますが。

Windows Subsystem for Linux上でphp-fpmを動かしてみた

最近のWindows 10にはLinuxバイナリをそのまま動かすような仕組みが導入されています。これは Windows Subsystem for Linux (WSL) とか Bash on Ubuntu on Windows (BoW) などと呼ばれているもので、VM実行でなくWindowsネイティブでLinuxを動かすという意欲的な取り組みです。


この環境で最新版のPHPソースコードからビルドし、アプリケーションサーバとして動作させてみました。


本稿執筆時点(2017年5月)ではWSL自体がまだ不安定な印象ですが、nginx+php-fpmを動作させることができました。以下はWindowslocalhost 80番ポートでPHPが動作している証拠画像です。「System」欄のunameの表示にMicrosoftという文字列が入っているのがオシャレですね。



以下、WSL上でnginx+php-fpmを動かすまでの手順を紹介します。

WSLのセットアップ

Bash on Ubuntu on Windows - Installation Guide」に従ってセットアップします。

PHPのビルド

まずはWSL上でPHPをビルドしてみましょう。


コンパイル済みバイナリをaptでインストールしてもいいのですが、普通にビルドできる程度にWSLが安定してるのかな?という興味から自前ビルドしてみました。


まずは必要パッケージをインストールします。

$ sudo apt update
$ sudo apt install build-essential libxml2-dev zlib1g-dev libcurl4-openssl-dev \
    libjpeg62-dev libpng12-dev libmcrypt-dev libreadline-dev libtidy-dev \
    libxslt1-dev libssl-dev libbz2-dev git autoconf


今回はphpenv+php-buildでPHPをビルドします。

$ curl -L https://raw.github.com/CHH/phpenv/master/bin/phpenv-install.sh | bash
$ mkdir $HOME/.phpenv/plugins
$ cd $HOME/.phpenv/plugins
$ git clone https://github.com/php-build/php-build.git


下記の内容を .bashrc追記してシェルを再起動します。

PATH=$HOME/.phpenv/bin:$PATH
eval "$(phpenv init -)"


あとはphpenv経由でPHPをビルドするだけです。

$ PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 phpenv install 7.1.4


ビルドにはかなり時間がかかるので注意してください。筆者の手元のマシン(Thinkpad X260、Intel Core i7 2.5GHz)で30分以上かかりました。同じビルドが12インチMacBook(Early 2016、Intel Core m5 1.2GHz)で10分を切ることを考えると、まだお仕事で使えるレベルではない印象です。今後のパフォーマンスチューニングに期待しましょう。


ちょっと遅いことを除けば、ビルド自体は正常に終了します。ここまでは普通のLinuxと大差ありません。

nginx+php-fpmの設定

つぎに、nginxをaptでインストールします。

$ sudo apt install nginx


設定ファイル /etc/nginx/sites-enabled/default の設定のうち、下記部分のコメントアウトを外して有効化します。

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass 127.0.0.1:9000;
        }


nginxを再起動します。

$ sudo service nginx restart


php-fpmの方はデフォルトの設定ファイルをコピーしてそのまま使います。

$ cd $HOME/.phpenv/versions/7.1.4/etc/
$ cp php-fpm.conf.default php-fpm.conf
$ cp php-fpm.d/www.conf.default php-fpm.d/www.conf


下記コマンドでphp-fpmを起動します。

$ $HOME/.phpenv/versions/7.1.4/sbin/php-fpm


ここで /var/www/phpinfo.php などを設置すれば、nginx経由でphp-fpmを利用することができます。


ただし、この設定だと1秒に1回下記のエラーが出続けます。getsockopt(2) の実装がまだ不完全のようですね。

[01-May-2017 20:06:06] ERROR: failed to retrieve TCP_INFO for socket: Protocol not available (92)
[01-May-2017 20:06:07] ERROR: failed to retrieve TCP_INFO for socket: Protocol not available (92)
[01-May-2017 20:06:08] ERROR: failed to retrieve TCP_INFO for socket: Protocol not available (92)


TCPでのプロセス間通信に問題があるならunix domain socketにすればいいじゃない、と考えてunix domain socketも試してみたのですが、phpinfo() の結果が途中で切れてしまうようです。こちらも実装が不完全なようで、16KB超のデータがうまく扱えないとか、そんな制限がありそうな挙動に見えました。TCPの方がまだ安定している状況だといえるでしょう。

まとめ

WSLはまだbeta版なので過度の期待をする方が悪いと言えばそうなのですが、まだ性能が出なかったり未実装のシステムコールもあったりで、お仕事で使えるようになる日は遠いかな、という印象を持ちました。


とはいえ、長期的に期待できる技術なのは間違いないところでしょう。さらなる安定と正式リリースが待ち遠しいですね。