ラジオネームポヨヨンさんから、「PHPがC言語のラッパーであるという話について詳しく知りたいです。」というお便りをいただきました。
ポヨヨンさん、ありがとうございます。 では今回はこれについて話していけたらなと思うんですけど、これどういうことですかね?
そうですね。多分なんかイベントとかSNSとかで、自分が少し言ってたことを深掘りしたいって感じかなと思っているので、
自分のPHPの経験を交えながら、自分からPHPがどう見えているのかっていうのを少し話せればなと思っています。
はい。ではお願いします。まずどこからいきましょうかね?
そうですね。まず、割と簡単めなFopenという関数についてちょっと話したいかなと思っています。
Fopen、ファイルオープンですかね?
そうですね。ファイルオープンする関数で、これPHP以外の言語でもあるんですけど、Fopenって第一引数にファイルのパス渡して、その後に文字を渡すんですよね。
例えばRとかWとかAとか、これPHP以外にもあるんだけど、
モードですかね?
そうですね。Rだともう書き込みできない、読み込みしかできないみたいな、Wだと書き込みしかできないみたいな、そういうのを文字で表すんですよ、関数として。
はい。よくある形かなと思いますね、これ自体は。Rubyとかにも似たような形であるかなと思いますね。
なのでこれ自体は他の言語にもあるんですけど、そもそもOSについて考えたくて、
リナックスカーネルについてですかね?
そうですね。今回リナックスカーネルについてなんですけど、まずファイルの読み書きって、ハードウェア、実際にディスクに対して読み書きしないといけないじゃないですか。
そうですね。
そういうハードウェアの制御ってアプリケーションからできないんですよ。
すべてリナックスカーネル経由でシステムコールされるみたいなことですかね?
リナックスカーネル経由でハードウェアって直接操作しないといけないのでアプリケーションからはできなくて、
アプリケーションからそのハードウェアを触るときどうしたらいいかっていうと、リナックスカーネルのシステムコールってやつを呼び出さないといけないんですね。
そうですね。
リナックスカーネルにはオープンというシステムコールがあるんですけど、このオープンのシステムコールはこのRとかWみたいなやつないんですよ。
なるほど。さっき言っていたモードというかオプションというものが存在しない。
そうですね。RとかWみたいなやつはとりあえずない。
じゃあどうなってるのって話だと思うんですよね。
はい。
で、オープンの場合はRD onlyとか、これがREAD onlyなんですけど、
WR onlyとかそういうのがオープンに渡すフラグっていうのがあって、それを渡すっていうのがリナックスカーネル側のシステムコールの仕様なんですね。
なるほど。オプションとして別の名前で存在してるっていう。
そうです。別の名前。RとかWとかではなくて別の名前なんですね。
なるほど。
なので、それをPHPが内部で変換してないとこれは実装できないはずなんですよ。
そうですね。マッピングしてないといけないですよね。
そうですね。
オプションを。
で、これなんで断言できるかっていうと、さっき言った通りそのハードウェアを直接操作できるのはリナックスカーネルしかないから、
必ずリナックスカーネルのシステムコール呼び出しているはず。そうなるとシステムコールの仕様と違うから、これは何かPHP側で変換をしてないとおかしいっていう話になってくるんですよね。
そうですね。そういう仮説が成り立ちますね。この時点では。
そうですね。で、じゃあどうなってんのっていうと、当然今話した通りになってるんですけど、それを知りたい時にどうするかっていうと、PHPの場合は関数名でPHPのソースコードを検索、普通にグレップするとだいたい出てきます。
今回の感じだったらFopen関数でグレップするっていう感じですかね。
グレップすればそうですね、このFopenって関数呼び出すとCのどの実装が呼び出されるかっていうのは結構簡単に見つけることができます。なのでFopenがどういう実装になってるか知りたかったらそこを読めばいいんですね。
グレップして探せばいいっていうような感じですね。
PHPってPHP.NETっていうドキュメントサイトがあって、このPHP.NET結構丁寧なので、基本的にはPHP.NET読めば大丈夫なんですけど、
Linuxのさっき言ったモード、そのOpenのモードについても実はPHP.NETに結構丁寧に説明されてるんですよ。
ちゃんとドキュメントがしっかりしてるんですね。
Rって出したらこういう挙動するんですよって説明がちゃんと書かれてて、基本的にそれ通りに動くんですけど、これってLinuxカーネルのどれに対応してるんだろうと思ったらソースコードを読みに行かないといけない。
それで対応してるモードを見つけてきたら、そのモード一つ一つの意味を調べていくと、確かにPHP.NETに書かれてる通りの挙動するねっていうのはそこで初めてわかるんですよ。
いきなりPHP.NETを見て、なるほどねって覚える人もいると思うんですけど、自分の場合、そこで覚わらなかったんですよね。
そこからさらに深く行ったっていうことですかね。
というかそのPHP.NET読んだ時に、ちょっと釈然としないというか、本当にそうなのかなってちょっと思っちゃったんですよ。
結論言うと、基本的にはあってると思うんですけど、PHP.NETに書かれてる内容って。
けどLinuxカーネルのこのモードに対応するからこうなってるはずで、PHP.NETの説明読んだら確かにそうなってるねって自分の中では納得できたんですよね。
という感じで、まずPHPのCの実装を読んでからPHP.NETの説明を読むことで、自分はFopenの挙動をちゃんと理解できたという感じだったんですよ。
ドキュメントを読むだけではちょっと納得感がなかったんだけど、実装まで降りた時に、確かにねというような形になった。
そうですね。確かにそうなるねまで行けたんですよ。
なのでそのPHPの、今Fopenの話したんだけど、そこだけでもC言語の実装を読んで初めて自分が理解できたので、結構あれってなるんですよね。
そのPHPのFopenを読んだ時は、このPHPのC言語、さっき読んだこのC言語の実装が呼ばれているからこういう挙動をして、
で、あたりが返ってくるんだなっていう発想に自分の頭の中でなってくるんですよ。
そうすると、PHP書いてる時に、これを読んだからこのCのこのコードが呼ばれているはずっていう発想になってくると、
これってCのラッパーじゃないっていう風に自分の中でだんだんなってくるんですよね。
この後も他の事例話していくんですけど、どんどんどんどんそういう事例が自分の中で溜まっていったっていうのが大きいですね。
なるほど、それが転じてというか、結果的にPHPはCのラッパーであるという言語化につながるってことなんですね。
そういう事をやっていると、だんだんPHPのコードを見ても全部、
これはCのこのコード呼ばれて、これはこのCのコード呼ばれてみたいな発想になってくるので、
なるほど。
やっぱりソースコードが読みやすいというか、この関数は、この関数、PHPのこの関数を読んだらここ呼ばれますよみたいなのが結構はっきり書いてあるんだよね、PHPの場合って。
なんか入り組んでないってことですかね、シンプルにこの関数だったらこのCの実装が呼ばれるよみたいな形でシンプルに書いてあるっていう感じなんですね。
そう、シンプルに自分の認識だと結構シンプルに書いてある、入り組んでる感じでは全然なくて、グレップするとすぐ分かるレベルなんだよね。
すごい、巨大システムなのかなってなんとなくPHP、言語の実装って結構複雑なのかなと思ってしまうんですけど、割とシンプルで分かりやすく書く。
もちろん色々、例えば変数の仕組みとか色々複雑な部分ってもちろんあるので、そんな単純ではないんだけど、
関数一つ一つの実装を知りたいだけだったら、割とグレップしたらすぐどこ読めばいいか分かると思いますね。
もちろん例外はあるかもしれないけど。
複雑なところはあるかもしれないけど、片杖さんが見てる範囲ではかなりシンプルで、しかも関数が呼ばれたらこのC言語の実装が動くんだろうなというところまで見えてくると、
やっぱりC言語、PHPや新言語のラッパーだよねみたいなところまで落ちてくるって感じですね。
そうですね。他にも例えばその似たような関数で言うと、ファイルプットコンテンツっていう関数もあって、そのファイル書き込みするような関数なんですけど、
このファイル、このファイルプットコンテンツ使った時に、そのLinux Kernelのオープンのモードってどれなんだろうっていう疑問が自分の中で湧いたことがあって、
それもやっぱり検索すると出てくるんですよね。オープンの時にどのモードを使ってるのかっていうのは割とすぐ調べられる。
そうすると、なるほどねみたいな。確かにこのモードだったらこういう挙動しかできないなっていうのがわかる。
具体的に言っていくと、例えばLinux Kernelのファイルモードだと、さっき言ったリードオンリーとかライトオンリーみたいな指定もできるし、
あとファイルが存在しなかった時に、作るか作らないか、その空ファイルを作るか作らないかっていうのもオークリエイトっていうのを渡すか渡さないかって選べたりとかするし、
あとオートランクっていうので、そのファイル開いた時に全部中身のデータ全部消すか消さないかみたいなのも選択できたりとかするんですよ。
かなり細かく指定できるんですね。
あとオーアペンド、自分がよく使うオーアペンドってやつで、これはログファイルとか書き込む時によく使うんだけど、必ず末尾にどんどん書き込むみたいな設定がオーアペンド。
追記ですね。
そう、追記しかできないってやつ。ログファイルとかに使うとすっごい便利。
便利ですね。確かに消さないから。間違えて。
そう、間違えて消したりしないからすごい便利なんだけど、この辺がどれ使ってるのかって、やっぱりある程度Linux Kernel勉強したことある人は気になるんですよね。
例えばさっき言ったオーアペンドついてるかついてないかで挙動って全然違うので、これどれなんだろうっていうのをやっぱり知りたいんですよね。
PHPのソースコードを見ていくと、このモードだったらこうなるねっていうのがわかるし、やっぱりその後にPHP.NETを見ると結構ちゃんと書いてあるんだよね、この辺って。
なのでPHP.NETを見て満足するんだったらいいと思うんだけど、自分みたいに満足できない人はソースコード読みに行って、
確かにPHP.NETに書かれてる通りの動きしそうってとこまで行くって感じです。
なるほど。ちなみにさっきの例で挙がってfputsでしたっけ?ファイルの書き込みの。
ファイルプットコンテンツですね。
ファイルプットコンテンツはオーアペンドなんですかね、モードとしては。
これね、最初WBだから、オートランク、オークリエイト、OWRオンリーで空っぽ。
まず空っぽにするし、あと空ファイル。ファイルがなかったら空ファイル作るし、あとライトしかしないっていう設定のはず。
なるほど。先に空にしてしまうんですね、逆に。
そうですね。いくつかオプションみたいなのが選べて、ファイルアペンドとかつけるとさっき言ったオーアペンドがついたりとか、いくつかモードが選べるはずです。
デフォルト挙動はさっき言ってもらったモードなんですね。
そうですね、さっき言ったやつ。その辺がやっぱりソースコードを見ると分かるんですよ。
このファイルモードだからこう動くなみたいなのが。ソースコードまで見るとすぐ分かる。
PHPのコードだけ、その関数名だけ見てると分かんないですよね。
分かんないです。逆に分からなくて、ソースコード、C5のソースコードまで読むとLinux kernelにどのモードで渡ってるのかっていうのが分かるので、そこまで見ると結構明確に挙動が分かる。
そこまでいくとあれですね、やはりPHPをちゃんと扱ってる感が出てきますね。ちゃんと実装を見て。
どのモードが使われているのかっていうところまで確認して、だからこれを使ってるんだっていうような確信が持てると思うので。
そうですね、この実装でいいんだっていう確信が持てるようになる。
やっぱりこの辺の理解が曖昧だと本当にこれでいいんだっけみたいな、例えば同時に実行した時にどういう挙動をするんだっけとか、
そういう細かいところが気になっちゃうんだよね、自分の場合。
やっぱりそうなるとLinux kernelでこうなってるんだったらこういう時はこういう挙動をするみたいなのが、やっぱりそこで初めて分かるんだよね、自分の中で。
腹落ちしますね。
腹落ちする。PHPの関数だけ見ても分かんないし、PH.NETには大丈夫って書いてあったとしても本当にそうなのかって自分はなってしまう。
確かにそう思いました。今の事例だけ聞いた感じでもそうだなと思ったんですけど、他の事例とかあったりしますか。
そうですね、これも意外とあれ知られてないのかって自分疑問に思ったりするんですけど、セッションですね。
例えばPHPだとドル&アバーセッションっていうのがすっごい便利で、ドル&アバーセッションにいろいろ入れるとログインしている、
そのセッション使える人が常に同じデータ見れたりとかするので、結構簡単にWebアプリケーション作れる。
PHPだとWebアプリケーション簡単に作れるのって、やっぱりドル&アバーセッションがすごく使いやすいからだと自分は思ってるんですよね。
それだけじゃないけどもちろん。
PHPのすごいところというか、他の言語と違うところって、このセッションの機能が言語に組み込まれているところなんだよね。
そうですね、なんか不思議だなと思ったのが、例えばRubyの場合はRailsとかにフレームワーク側についてるかなと思うんですけど、
PHP言語に入ってるんですね。
そう言語に入ってて、これ多分自分が知る限り言語に入ってるのは多分PHPしかないんじゃないかな。
多分Railsとかだとクッキーとかレディスとかいくつか選べると思うんですけど、
その辺の実装もRuby上で自前でやられているはずだし、あとGoも標準パッケージはないんですよね。
ゴリラセッションっていう割と有名なライブラリーがあったりとか他にもあると思うんだけど、
そういうライブラリーを使ったら提供できる機能なんですよ、セッションって。
フレームワーク側のセクムなのかなっていうのを若干思ってしまうんですが、言語でサポートしてるんですね。
そう、PHPは言語なんですよ。これが個人的には結構クセものだと思ってるんですけど。
クセものなんですね。便利ではなくてクセもの。
一見便利だし、自分も昔は便利だともちろん思ってたんですけど、なんかハッと気づくというか、
例えばPHPのセッションだとその同じユーザー、例えばそのユーザーの有効なセッション全部消したいですとか、
要はそのPHPのセッションが提供しない機能をやろうとした瞬間に詰むんですよね。
言語がサポートしているがゆえに拡張性がないということですよね。
ちょっとこの後もその話もするんだけど、そのPHPのセッション使って便利便利と思ってたら、
ある日突然今ログインしているこのユーザーの人のセッション全部消してほしいって言われた時にやる方法がないと。
存在しないんですね。
そうですね。なのでそのPHPのセッションってすっごい便利なんだけど、その代わり完全に言語に組み込まれてしまっているので、
その機能から逸脱したような機能を提供しようとすると、急にどうするのってなってしまう。
なるほど。
で一応ね、全くないわけじゃなくて、PHPでセッションハンドラーっていう機能があって、
これインターフェースかな。だからインプリメンツでセッションハンドラーインターフェースっていうのをインプリメンツすれば、
PHPのセッションの挙動を上書きできるっていう機能があるんですよ。
なるほど。オーバーライドできるんですね。
なのでここでオーバーライドをうまく使えば、さっき言ったような機能っていうのは作ろうと思えば作れる。
要はそのユーザーで有効なセッション全部削除するみたいな機能も頑張って
セッションハンドラー自分で作ればできる。
じゃあそこが一応の拡張ポイントになってるんですね。
そうですね。一応できるはできるんだけど、急に難易度は上がってしまう。
このPHPのセッションハンドラーって、PHPある程度使ってる人じゃないとたぶん知らないんだよね、そもそもこの機能って。
もちろんPHPの1機能だし、PHPでコードを書けるんだけど、ある程度PHP使ったことないとたぶん知らない機能だろうし、
あとこれでもまだセッションハンドラーでもまだ解けない疑問ってあって、
このセッションハンドラー関数いくつか定義するんですけど、この関数呼び出してるの誰っていう話になるんですよね。
なるほど、セッションハンドラーで定義した関数を呼び出す。
実装はないんですよ。
実装は何なのか、PHP自身であるっていう。
それは実は、我々が書いたようなPHP自体のソースコード。
アプリケーション側にはないってことですね。
アプリケーションコードにはなくて、この関数を呼び出すコードってどこにもなくて、
PHPの内部実装でその関数は内部的に呼び出されてるんですよ。
エントリーポイントを書くところがないってことですね、PHPのアプリケーション側に。
アプリケーション側にはない。
ってことは、PHPはCで書かれているので、ということは、
PHPのセッションはCで実装されてるってことなんですよ。
そうですよね。
当たり前なんだけど、やっぱりセッションってウェブアプリケーションの中では一般的すぎるというか、
めちゃくちゃよく使う機能じゃないですか。
絶対に使えますよね。
最近は使ってないアプリケーションも増えてると思うんで、
絶対とは限らないんだけど、やっぱり一昔前のウェブアプリケーションだったら絶対使ってるし、
今でもやっぱり超根幹機能なんですよね、ウェブアプリケーションの。
なのにそれがCで実装されているので、内部実装を読み解くのが極めて困難っていうのがPHPのセッションなんですよ。
なるほど。ちなみにさっきの話だったら、関数単位だったらCの実装を見るのはシンプルで見やすいという話だったんですけど、
このセッション周りの実装はどうなんですかね。
実は自分読んだことなくて、というのは一回読もうとしたんだけど大変すぎて諦めたんだよね。
じゃあここは複雑な可能性があると。
複雑というか、そもそもやってることを想像してほしくて、
セッションってまずセッションのIDを生成しないといけないじゃないですか、ランダムの。
ランダムID生成する。
でそれをクッキーに付与しないといけないし、そのセッションのIDをキーにして、
どっか、PHPのデフォルトがファイルなんだけど、そのファイルに、確かあれファイル名だったかな、にセッションIDをファイル名にして保存して、
でそのファイルの中に、PHPだとシリアライザーが内部で入ってたりするので、そのシリアライザーを使って書き込んでたりするんですよね、そのセッションに入ってるデータを。
みたいなのをやりつつ、
あと、例えばそのセッションIDをリジェネレートするって関数呼び出されたら、まだリジェネレートするとか、
そういうのを全部一個一個、Cで全部作られてるんですよ。
セッションって意外とやってること多いんですよ、実は。
うん、そうですよね。
そう、なので、これを全部Cでやられてるんですよね。
他の言語は、やっぱり例えばRubyとかGoだったら、そのGoとかRubyのコード読めばセッションがどういう挙動してるかってすぐ分かるのに、
PHPはCのコードをかなり頑張って読まないと、挙動が分からない。
しかも、あれですよね、言語に組み込まれてるからこそ、言語特有のコードとかもちょっと見ないといけない。
見ないといけないし、結構内部にあるんですよ、そのセッションのコードって。
だから、読み解くのがだいぶ難しい。
なるほど、難しそう。
そうなんですよ。
なので、じゃあセッションハンドラー使えばいいかっていうと、やっぱりセッションハンドラー自体を呼び出すエントリーポイントっていうのは、やっぱりアプリケーションコードには現れてこない。
C。
セッションハンドラー使ったら分かりやすくなるかっていうと、正直そうでもないんだよね。
結局、上書きできる挙動って一部だけだし、これで大体のアプリケーションは困らないと思うんだけど、それでも難しい。
あとセッションハンドラーって、実はCでも書けるんですよ。
なるほど、ネイティブで書けるんですね、そこだけ。
そうなんですよ。
例えば、Ledisのライブラリーがあるんですけど、Ledis用のセッションハンドラーがCで実装されていて、
PHP上に設定渡すと、それ呼び出してくれる?
いや、PHPのセッションってデフォルトファイルなんで、ファイルだと複数代行せんできないんですよ。
確かに。
アプリケーションサーバーが。
分散できないですよね。
そう、なのでアプリケーションサーバーを複数代にしたいってなると、ファイルは絶対できなくて、
必ずLedisとか何らかのデータベースに入れる必要があるんですよ。
外部に保存する必要性がありますね。
そうですね、なので結構Ledisのセッションハンドラーは使われていると思うんだけど、
あれもCで実装されているので、Ledisのセッションハンドラーの挙動を読み解きたいんだったら、Cの実装を読まないとわからないです。
なるほど、難しいですね。
そう、だからセッションハンドラーを、PHPのセッションでやっていることを読み解こうとした瞬間に、基本的に全部Cなんですよ。
だから、PHPを書いていたと思いきや、いや違う、Cを書いているんだ、Cを読んでいるんだ、我々は、みたいな感じになっているんですね。
実はセッションのコードを読んでいるってことはCのコードを実行されているし、セッション周りで何かやりたいことが出てきた場合は、既存の実装はCで実装されているので、Cの実装を読まないと何したらいいかわからないですよ。
難しいです。
そう、これね、自分は何回かPHPのセッション周りを触ったことがあるんですけど、今はAIが出てきたんでめちゃくちゃ楽になったんですけど、AIが出てくる前は結構大変でしたね、これは。
今だったら読み解くのは、Cをちょっとここを読んで、みたいな感じで。
そう、AIにコードを読ませて挙動を確認すれば、それをPHPのセッションハンドラーで再実装するっていうのも多分そんなに難しくはないので、
今なら多分できるんですけど、実はちょっとやったんですけど、今だったらそこまでじゃないんですけど、やっぱりひと昔、AIが出てくる前は本当にCを読まないといけなかったので、だいぶ大変でした。
やっぱ、PHPを書いてるんじゃないんですね、PHP越しにCを書いてるって感じですね。
このPHPのセッションを使うと、実はCが呼び出されているので、あ、Cだなっていう気持ちに自分とかはなっちゃいますね。
PHPの気持ちがわかるっていうことは、それイコールCで書かれたPHPの気持ちがわかるっていうことですもんね、透過的に。
どうなんですかね、その辺は多分人によって印象違うと思うんですけど、正直自分はさっき言ったFORPUNとか、あとPHPのセッション周りとか、そういうのいろいろ見ていって、全部結局C読まないと挙動わかんなかった経験があるので、結局Cじゃんっていう気持ちはあるんですよね。
やっぱここでも、PHP越し、真のラッパーであるというような結論に。
そう結局PHPやってるはずなのに、なんかずっとC読んでんなみたいな気持ちにだいぶなってる、自分は。
なるほど。他の事例か、もしくはちょっと違う方向性とかってあったりするんですかね。
そうですね、実は前回も言ったかな、イスコンボンっていう本を自分は著者の一人なんですが、そのイスコンボンで紹介している文章をちょっと今から読み上げるとですね、
マルチプロセス、シングルスウェットのアーキテクチャーの場合、簡単にはプロセス間でメモリーを共有できないっていう文章が書いてあって、その次に、
PHPで利用されているAPCUでは、共有メモリーセグメントを利用して複数のプロセスが物理メモリーの同じ領域、セグメントと呼ばれる、を共有している。
その次にAPCU自体はC言語で実装されており、他の言語で簡単に利用できる仕組みではないっていう文章が実は書かれてるんですよ、イスコンボンに僕が書いた章に。
実はこの文章を書くためだけに、当時自分はPHPのAPCUっていうC言語で書かれているライブラリを数時間かけて頑張って読んだんですよ、実は。
すごい、この数行のためだけに。
そう、この数行のためだけに自分は何時間もかけてC言語のコードを読んでたんです。
すごい、何て言うんでしょうね、すごい努力の結晶がここには詰まってるんですね。
そう、なんか本読むと多分数行しか書かれてないからサラッと読み飛ばすと思うんだけど、この数行書くためだけに自分は結構な時間をかけてるんですよ。
で、イスコンボンってAIが出てくる前に書いた本なんで、本当に自分で読むしかなかったんですよ。
今だったら、ちょちょいでもしかしたら終わる可能性。
今だったらAPCUのソースコードをAIに読み込ませて多分すぐできたと思うんだけど、当時はそんなもんないから頑張ってコード読んでこの文章を書いたんです。
すごい、ちゃんと確認してちゃんと書いたっていうような形なんですね。
そうですね、ちょっと正直例外として片付けることもできなくはなかったんですけど、やっぱりイスコンボンって技術書だし、絶対なんかあれじゃあAPCUってどうなのって話になるなと思ったので、
ちゃんとここは疑問解消しておきたいと思ったので、コードちゃんと読んでどういう挙動しているのかっていうのを調べた上で書きました。
なのでちょっとAPCUの話をしたいなっていうところですね。
このAPCUって何ですかね。
APCUはそのPHPでインメモリキャッシュをしたい場合に使うライブラリーです。
なるほど、キーバリューで保存しておける仕組みみたいなところですかね。
本当にキーバリューで結構軽量で使えるんで、PHPでやっぱりある程度大きいハイパフォーマンスのウェブアプリケーションを作りたい場合はAPCU使わない手は基本ないんじゃないかなと思いますね。
これはAPCUはインメモリキャッシュです。
あれですかね、REDISと似たような形なんですかね。
REDISと似たような感じなんですけど、REDISだとそのPHPからREDISだと1回TCP通信しないといけないので、
大量に通信するとレイテンシーが大きくなったりもするし、REDIS側の負荷にもなったりする。
なるほど。
APCUはインメモリキャッシュなんでサーバー間で共有というのはできないんだけど、その代わりやっぱり最速なんですよね。
PHPを使う上では最速のインメモリキャッシュなんですね。
そうですね。やっぱりその外部のキャッシュ使っちゃうとやっぱり通信コストっていうのがどうしても発生してしまうので、
インメモリキャッシュってやっぱり早いんですよ。なのでそのインメモリキャッシュ、PHPで使いたいってなったらAPCU以外の手は基本ないと思いますね。
このAPCUっていうものは外部のライブラリーとして存在しているっていうことですね。
まあライブラリー、そうですね。
言語の機能ではないってことですよね。
言語の機能ではないですね。でもなんかAPCUって本来おかしいですよ。
PHPってさっきちょっと説明した通りマルチプロセスシングルスレッドのアーキテクチャなので、
そのPHP1プロセス1プロセスは本来メモリ共有できないはずなんですよ。
なのでインメモリキャッシュって本来使えないはずなんですよね。PHPのアーキテクチャ的には。
それを飛び越えてきてるんですね。
そう、それを飛び越える仕組みがAPCUなんですよね。
なんかすごいことやってそうなんですけど。
そう、だからこの説明文書いたときに絶対疑問に思うなと思ったんですよ。
PHPである程度の規模のサービスやってたらAPCUって多分大抵使われてると思うんで、
この文書を読んだときにあれじゃあAPCUってどうなってんのって、
少なくとも自分はなるなと思ったんですよ。
だからちゃんと調べたんですね。
だからちゃんと調べてAPCUは共有メモリセグメントを利用してますっていうのをはっきり書いた。
すごい、素晴らしい。
共有メモリセグメントというものを使っていれば同じような仕組みが作れるっていうか、
使えるようにAPCUみたいなことが使えるっていうことなんですね。
そのマルチプロセスのアプリケーションでもMMAPでMAPシェアドっていうのを使えば、
共有メモリセグメントっていうのを使えて、プロセスをまたいで共有領域っていうのが作れる。
その中にAPCUの関数呼び出したらその中にデータを保存していくっていうことができる。
MMAPっていうのはシステムコールですか?
システムコールだったんじゃないか。システムコールのはず。
リラックスパネルの機能ですね。
そうですね。
これを調べるには当然C言語のソースコードを読まないとわからないんですよ。
だから当時の自分は頑張ってC言語のコードを頑張って読んでこの文章を書いたんです、当時。
やっぱりここでもPHPの言語の特性上絶対にできないようなアーキテクチャなんだけども、
それを飛び越えてきているのはどうやるのかを調べるためにはC言語を読む必要性があったっていうところですね。
そうですね。
なのでこういうのを考えると、あれ結局PHPなのに俺はずっとC言語を読んでるぞっていう気持ちにどんどんなっていくんですよ。
PHPを読むっていうことが基本的にアプリケーションだけでその下に行こうと思うとすぐCになってしまうみたいな感じなんですかね。
そうですそうです。自分もその感覚で。
PHPのアプリケーションコードを書いてるだけだったら基本PHPの世界しか見ないんですけど、
あれじゃあ結局これってどうなってるのとか、サーバー並べた時にどうなるのとか幸福感だった時どうなるのとかちょっとでも疑問に思うと全部Cなんですよ。
PHPはCで作られてるから。なのでちょっとでも疑問を持った瞬間にC言語のコード読まないとどういう動きするかわからないっていう世界ですねPHPは。
だからこそラッパーであるって感じ。
だから自分はPHPはCのラッパーだよねっていうのをたまに言ってることあるんだけど、それはこういう経験をいろいろしてきたからになりますね。
逆に言えば今だったらパッと調べられるわけじゃないですかAIによってここの機能ここの実装どうなってるかとか、
あとはここの仕様どうなってるかなんでさっきの話で言うとAPCなんでPHPで使えるのみたいな感じで調べてもらうわけじゃないですかAIによって。
ただそうやって調べてもらうとパッと出してもらったからC言語を読んでるっていう気持ちにはならないですよね現代であれば。
それはAIの指示の仕方かなと思ってて。
AIに指示出すときに実際のコードどこですかとかって聞けばこのコードですよって教えてくれるしこのコードどういう行動しますかって聞いたら
このCのコードが呼ばれててこのCのコードはこういうコードですよとか今のAIだったら全然教えてくれるんでそこやっぱ内部に興味持つかだと思いますね。
目的意識次第ですねそこは。
そうですねなんかAIに聞いて回答もらったら終わりって感じだったら確かにCは読まないかもしれないけど回答もらうプラス実際のCのコードを教えてもらう。
でそのCのコードをもっと詳しく教えてとか言えばどんどんどんどん潜っていけるし今は本当に潜るのが簡単になったんで本当に昔はCあんま読んだことないのにって思いながらずっと読んでたんで今は本当に楽ですよそのあたり。
素晴らしい。
なんというかCあんま書きたくないからPHPとかそういうRubyとか書いてるんだけどなみたいなそういう側面も多分あるかなと思うんですけど。
ウェブアプリケーション作ってるはずなんだけどなーみたいな気持ちにだんだんなってくるんですよね。
ちょっと辛い気持ちになるけどもでもそれが故に結構勉強になるところもあるっていうような。
そうですね。
ありますよねそこは。
じゃあちょっと次がですねこれが結構具体的に困ったやつの話をするんですけど。
以前Memcache-Dを自作してみたって話をした回があったと思うんですけどその時に実はちょっと前振りしてたなってことを思い出して。
Memcache-Dプロトコルの話をあの時もしたと思うんだけどその時に実はMemcache-Dってキーとバリューだけじゃなくてフラグスっていうイントの値もキーとバリューとは別に保存できるって話をちょっとしてて。
はいはい。
いやその時に実は自分ちょっとポロッと確認したら言ってたんですけどあのこのフラグのせいでたまになんかトラブルになることあるんだけどみたいなことをちょろっと言ってるんですよ実は。
でそれが今から話す内容です。
実際にフラグでトラブったって話を今からします。
ちょっと繋がりますね。この回聞いててちょっとMemcache-Dの話聞きたかったらMemcache-Dの回は2回目ですかね。
何回目だっけ。なんかリンク貼っておきましょうか。
概要欄にリンク貼っておくのでぜひ聞いてみてください。
でですねなんか当時何があったかっていうとPHPって実はMemcache-Dが使えるライブラリーが2つあってMemcacheとMemcache-Dっていうちょっとややこしいんですけど2つあるんですよ実は。
Dがつくかつかないかですね。
はい。けど正直Memcacheの方は覚えなくてよくて何でかっていうとこいつはPHPならけにいけなかったんですよ。
移行がされなかった。
そうですね。PHPってPHP5系からPHP6の開発は失敗していてPHP5.6からPHP7.0にジャンプしたんですよ。
で5.6から7.0が結構完成がなくて特にぺくるのC拡張ぺくるってPHPのライブラリーを入れるときに使われてるやつでそのC言語で拡張するやつっていうのは大体ぺくるを
たぶんC言語の場合ぺくる使う方法しかないんじゃないかなって思うんだけどそのCで実装されているライブラリーはぺくるを使うんですね。
でそのぺくる系が特に結構全滅したんですよ実はPHP5.6から7.0に。
かなり破壊的変更があったってことなんですね。
そうですねその内部の表現とかも変わっちゃったんでガラッと。なので基本全滅していてそのPHP7に行けたやつはちゃんとメンテナンスされてるやつだけだったんですよ。
でMemcacheは行けなくてMemcache Dしか行けなかったんですねPHP7系に。
じゃあ考えるべきはMemcache Dの方ですね。
そうなんで今は2つあるって言っても今は片方はメンテナンスされてないから今からやるんだったらMemcache Dなんだけど当時自分が行った会社はMemcacheの方を使ってたんですよ。
古い方を使ってたんですね古い方というか。
そのPHP7に行けなかった方を使っていてあのPHP7に行きたいのでじゃあMemcache Dに移行しましょうってなってで何も考えずにMemcacheからMemcache Dに変更したんですよ。
それで動くでしょうと思ってやったらなんか動いたり動かなかったりするってことがわかったんですよ。
微妙に差があったんですね実装に。
そうなんか微妙に差があるっぽくてなんか取れるデータと取れないデータがあるってことがわかって。
けどドキュメントに何も書いてないんですよそんなこと。
でここからどうしたかっていうと当然のごとくCのコード読みに行ったんですよ。
だってドキュメントないから。
当然ですね。
そう当然のごとくだってどっちもCで書かれてるからCのコード読みに行かざるを得ない。
あの比較ができますねどっちもCで書いてるなら。
どっちもCで書かれててで仕様についてドキュメント何も書かれてないからもうC読みに行くしかないと。
でC読みに行ってわかったんですよ謎が解けたんです。
何が?
あのさっき言ってさっきから言ってる通りそのFlagsってやつがあってそのFlagsMemcacheDにはそのFlagsっていうのはアプリケーション側というかそのクライアント側が好きに使っていい値なんですね。
でMemcacheとMemcache DでそれぞれFlagsを使っていて
例えばなんかその
なんかその例えば0数字は適当なんだけど0だったらIntとか1だったら文字列とか
あとこの値だったらGzipで圧縮してますとか
なんかそういうのがFlagsに全部入ってたんですよ。
でその対応が当然あのそれぞれ好き勝手にやってる値だから当然対応しないんですよ。
だからたまたまそのFlagsが合ってるとかそういうのだったら読み込めるんだけどFlagsが違ってたら読み込めない。
なるほどデータの移行ができなかったってことですかね。
まあそうですねその互換性がないからMemcacheとMemcache D単純に書いただけだと無理だし
あと厄介だったのがそのFlagsはそのMemcacheのクライアントライバリー内部で使われている値で
こっちから取れる値じゃなかったんですよ。PHP側から取れる値じゃない。
なるほどCのところで収まってるというか
そうCのところで収まっちゃってるからPHP側ではどうしようもない。
内部に実装にまでちょっとあのパッチみたいなものは当てないといけないくらいの形だったんですね。
やるとするとC言語の方にパッチ当てて対応させるって方法はなくはないけど正直現実的じゃない。
難しい。取り得る方法としては2つあるのかなって今パッと思ったんですけど
そのパッチを当てるか既存のデータを諦めるか。
そうです基本的にその2択です。Cにパッチ当てるか既存のデータを全部諦めるか。
で既存のデータを全部諦めるっていうことに当時なったんですよね。
そっちにしたんですね。でもメモキャッシュDに保存されているものに関しては全部キャッシュですもんね。
てはもうじゃないですか。
だからメモキャッシュDは入れちゃいけないんですよ。キャッシュ以外入れちゃいけないんですけど。
世の中はそうなってないことがあるんです。
そうですね現実はね。
現実はそうなってないこともあるので消えたら困るってデータがメモキャッシュDに入っていることもたまにあります。
つらい。一気につらみが増してきたんですけど。
そうですねなので結構大変だったんですけどそれは置いといて。
結局これはC読まないとわからなかったしそもそもメモキャッシュDにフラグスって機能があることも当時の自分は知らなかった。
Cのソースコードを読みに行ってあれフラグスってのがあるぞってことを知ってあれこれフラグスの値の対応関係めちゃくちゃやんってことに気づいたんですよ。
ライブラリ実装というかクライアント側の実装によって同じメモキャッシュD使ってるはずなんだけど全然挙動が違う場合があるよってことですもんね。
そうですね。
例えばGoのメモキャッシュDのライブラリ一番有名なやつってブラッドフィッツっていうメモキャッシュDの作者でもありGoの作者でもあるっていう人がブラッドフィッツなんですけどその人が作ったライブラリがあってその人のライブラリだともうそのキーとバリューとフラグスが普通にGoの構造体で入って帰ってくるんでそのフラグスの値によって挙動が変わるってことはそのライブラリだとないんですよね。
ちゃんと透過的になってるんですね。
そうですね。ただGoの場合はバイト列で扱えるから正直そんなにフラグス使う意味があんまないっていうのも大きいんですけど。
ただPHPの場合ってやっぱりそのPHPの変数にマッピングしないといけないからやっぱりフラグス使ってどの変数にマッピングするかとか判定したくなるんですよね。
だから結構Goとは全然事情が異なるっていうのはあります。
言語使用的にマッピングするための機能としてフラグスをPHPの場合は使ってたってことですよね。
そうPHPの場合はそのどの変数にマッピングするのどの変数に入れるのかっていうのをマッピングするためにそのフラグスを使っていたライブラリ側で。
だからPHPからは触れない領域に入ってたんですよ。
しょうがないというかなんていうかって感じですけど。
そうですね。なのでこれはもうどうしようもないという感じでしたね。
この事例でもやっぱC言語読んでちゃんと挙動を把握した上で移行せざるを得なかった。
その移行の計画をとってる際にCのコードを読まないとそもそも何が起こってるのかすらわからなかったっていうところですね。
逆に言えば片瀬さんがこれ移行しようっていうタスクやってたわけじゃないですか。
メモキャッシュからメモキャッシュDにぺくるの。
その時にちゃんと先によく気づけましたね。この値は読める、この値は読めないみたいな。
実際に開発環境で試してあれなんかこの値は取れるけどこの値は取れないよっていうのは結構すぐにわかったはずですね。
じゃあ明らかだったんですね。
明らかに取れたり取れなかったりするっていうのが明らかだった。そんぐらいぶっ壊れてたんですよ。
なるほど。でも逆に言えばそのぐらい根幹の機能ですよねこのフラグスに関しては。
そこがずれてしまうというのは無理ですね。
しかも結構賢いことをやっちゃってたんですよね。やっぱりその例えば何キロバイト以上だったら勝手に圧縮してフラグスの値これにするとか
なんかそういうのが結構ガッチリ書かれちゃってたからこれは無理だねってなっちゃったんですよ。
結構クライアント側が重厚だったんですね。スマートクライアント的な感じで。
スマートクライアントってことじゃないんだけどやっぱりそのフラグスをうまく活用してるんで
NPCの使い方としては間違ってないんだけどライブラリ移行しようとするとPHPから触れないところで色々やられちゃってるから
これはそもそも無理だねっていう。
結局そのケースだとMemcachedに行けないMemcachedのまま行けないんだったらMemcachedプロトコルである必要性もないよねっていう話になって
結局Memcachedプロトコル自体使うのやめちゃったんだけどその時は。
そうなんだ。
だからデータ自体本当に全然違うデータベースにそれこそRedisとかMySQLとかそういうところに移したんだけど
そのレベルの話になっちゃったので結構正直当初の想定よりもだいぶ大事にはなったんですよねこの時って。
ミドルウェアを入れ替える必要性があった。
ミドルウェアの入れ替えになったし全データのマイグレーションもあったから結構な数あったんで
想像よりはるかに大事になったんだけどやっぱりそういうのもやっぱりCのコード読んでこれはそれしかないですねっていう確信を持った上でそういうことをやれたし
やっぱりその結局Cのコード読まずになんかよくわかんないけど壊れるから無理じゃないですかとか言ったりとか
PHPバージョンアップなんかそもそも無理じゃないですかって話したりとか
もしくはなんかよくわかんないけど別のミドルウェアにすればいいんじゃないですかとかなんかそういうの自分は良くないと思ってて
やっぱりちゃんと根拠を持って
もしミドルウェア変えずにいくんだとしたらCのこのペクルのCのコードをこの辺全部書き換えて対応関係一致とすれば一応理論上いけるはずですけど
そんなこと本当にやりますっていうところまで話せたからこそこういう話になったんだよね。
いや素晴らしいめちゃくちゃちゃんと仕事してますね
そうですねなんなのでなんかこういうのをいろいろ自分やってきたから結局PHPって言いつつ自分はCばっか読んでたんで
Cじゃんっていう感じですね
結果Cだよねっていう
結果Cずっと読んでるみたいな
感じにもう確信ですねはいCです
これはCですね
ここまでいろいろ話すとCだねっていう気持ちに多分なってくれたんじゃないかと勝手に思っています
いやあの聞いてた僕自身Cだねって感じになってるのでこれを質問してくれたねポヨヨンさんも
それはCだわって思ってくれてるんじゃないかなと思うんですけど
そう言ってくれたら嬉しいですね
ちなみに他の話題とかはあったりしますか
まぁ一旦PHPがCのラッパーって話はこんなところかなと思うんですけど
なんか補足するとRedisには一応このFlags相当の機能はないです自分が調べた限り
なのでRedisにはそういうのはないかなと思います
でそれで言うとPHPのRedisのライブラリだと実はシリアライズの方法が選べます
例えばこのPHPの配列とかなんかそのメミキャCDとかにも保存できるんですけど
その時に実はシリアライズの方法って指定できないんですよ
それはメミキャCDのライブラリが勝手に判断してシリアライズしてそのシリアライズ処方をそのFlagsに保存してるからなんですね
けどRedisはそのFlagsの使用ないので
そもそも文字列しか保存できないので何かしらの方法でシリアライズしないといけないんですけど
この時のシリアライズ処方をPHPのRedisのライブラリはPHP側で指定しないといけないです
確かデフォルトはPHPってこれもたぶん珍しくはないか
PHPってシリアライズっていう関数があって組み込みでシリアライズできるって関数があるんですよオブジェクトを
それを使ったやつが確かデフォルトだったかな
けどJSONとか他のシリアライズ方法も設定すれば使うことができます
なのでこの辺は何でメミキャCDのライブラリはシリアライズ方法を指定しないのに
Redisの方はシリアライズ処方を指定するかっていうとこれはRedisにFlagsの使用がないからでもあるんですよね
やっぱりそういうメミキャCDの使用を知ってるからこそ
Redisが何でこういう仕様になってるのかっていうのが見えてきたっていうのも一つありますね
似たようなキーワードでの保存なんだけども
そのFlagsがあるかないかによって命令の仕方が違うよっていうところですよね
クライアントライブラリの実装方法が違うよねっていうところですよね
確かにそれはそうだなと思いますね
Redisの方がなんとなく拡張性あるしシンプルでいいなぁとは思いますけどね
今の話聞いてる感じだと
どっちがいいか人によるとは思うんだけど
自分の場合はそのFlagsで結構痛い目見たので
このFlagsの使用は正直やめてほしいなと思ってるんだけど
ただ便利なのはすごいわかるんですよね
キーとバリュー以外にももう一個値保存できたら
いろんなことできるよねっていうのは確かにそうだし
実際今説明した通りいろんなことやってるライブラリあるので
実際結構いいと思うんですよね
使い方によっては
どっちがいいかは難しい問題ですね
やりたいことによるけども
Flagsの方がちょっと暗黙的になりがちかなとは思いますね
隠されがちかなとは思いますね
実際Cのコード読まないとわからないところに入っちゃってるので
今回のケースはそうでしたね
よしよしですね
そこに関しては
別のシステムに移行しにくくは絶対になるはずなんで
そこをどう捉えるかもあるかなとは思ってます
っていうところですかね
そうですね
なんで自分趣味だと個人だと語以外ほぼ書かないんですけど
なんで自分が語ばっか書くかっていうと
この辺の経験もあって
結局語って語で書かれてるから
語ってわかんないことあったら語のコード読めばだいたいわかるんですよ
なるほど
言語が統一されてるんですね調べるときに
語って標準ライブラリーもそうだし
あとコンパイラーも基本語で全部書かれてるから
ひたすら語を読んでいけばわかるんだけど
PHP C読まないと結局どうなってるかよくわかんないから
C読まないといけないんですよ
なのでC僕そんな得意じゃないんでめんどくさい
なるほど
そういうところも言語を選定する理由にはなり得ますね確かに
そうですね
っていう話をするとあれじゃあ語ってどうやって始まったのって話が多分出るんですよね
語コンパイラーの作り方ってことですよね
語コンパイラーって最初語なかったはずなのにどうやって作ったのって話があると思うんですけど
これあのちょっとだけ補足しておくと
語は語1.4まではCで書かれてるんですよね
語1.4がCで書かれた最後の語コンパイラーなので
語1.4ってバージョンは今でも特別視されているバージョンです
で語1.5から語で書かれてる語コンパイラーなんで
語1.4をダウンロードしてきてその語1.4をCコンパイラーでコンパイルすれば語1.4が手に入るので
それを使ってあの語をコンパイルすれば一応語は手に入る
で今の語はあのブートストラップの語が新しくなってるので
今の最新の語を使うには
ある程度の語をその語1.4でコンパイルして作った語を使ってコンパイルして
さらにその上のバージョンをどんどんコンパイルしてっていうのを何回か繰り返すと
今の最新の語が手に入るはず
なるほどチェーンしてるんですねコンパイラーを
語自体が結構久しく語って語1.4の記法しか使えなかったんですよ結構何年も
確か数年前ぐらいまでずっとそうだったんだけど
さすがにそれだと語コンパイラー新しい語の機能が一切使えないから語コンパイラー側で
さすがにしんどいってなって
ブートストラップする語のバージョンもバージョンアップしていきますって方針に変わったんですよ
なるほど
なので今は語コンパイラーでも新しい記法使える代わりに
今から新しく語コンパイルで手に入れようとすると
何回か語のコンパイルを繰り返さないと手に入らなくなった
すごいなんか面白いですよねセルフコンパイルできる
セルフホストって言うんだけどセルフホストしようとするとどうしてもこうなっちゃいますね
なんか呪術つなぎというかすごい依存関係なんだろうなとは思いますけど
でもGoを使うエンドユーザーというか開発者からしたら
Goを見ればいいからすごいシンプルですね
Goを見ればいいからシンプルなんだけど
そういうブートストラップ問題みたいなのはあって
作ろうとすると呪術つなぎにどんどんどんどんやらないと手に入らない
PHPはCで書かれているので基本的にはCコンパイラーでコンパイルすれば
PHPは手に入るけど
Goは何回も何回もコンパイルしないと手に入らないっていう違いはありますね
だからGoのGo言語自体の開発者の方はもしかしたらすごく大変かもしれないっていう
基本的にはすでにGoはあるのでバイナリーが
それを使ってコンパイルすればいいので
実際には今言った手順やるっていうのは本当に少数派
例えばインテルとかARMとかのCPUじゃなくて
新しいプロセッサーでGoを動かしたいって場合は今言った手順を踏む必要があるので
そういう今Goが対応しないプロセッサーでもGoを動かしたいみたいな人がやる手順ではあるんですよね
今言った手順は
なるほど
特殊ではあるしもうすでにバイナリーが存在するからスキップできるよねっていう
実際にやる人はほとんどいない
なるほど
なんだけどそういう違いはありますねというところですね
だからGoを使ってるんだよっていう
自分がGoを使ってるのはCを読みたくないからっていうのが結構大きい理由の一つです
それはなぜかというとPHPという歴史があるからですね
PHPであれこれPHP書いてるはずなのにずっとC読んでるぞみたいになった経験があるからです
ありがとうございます
これお便りいただいたポヨヨンさんもですね
これちょっと聞いていただいて納得していただけたかどうか
コメントとかお便りでもっかいくださるとすごい嬉しいのでよろしくお願いします
では今回はここまでにしたいかなと思います
ヤウヨルズのOSSでは毎回一つのOSSを取り上げて
それについて片付いてヘンテコで技術的なところも深掘りしながら話をしていく番組になっています
今後もですねその名の通りヤウヨルズのOSS取り上げていけたらなと思ってますので
ぜひお聞きのプラットフォームでフォローと高評価のほうをお願いします
またですね今回みたいにお便りいただいて
PHPらしいラッパーであるこれはどういうことだみたいな感じでコメントとかいただけたら
どんどん取り上げていきますので
ぜひコメントとかあとはXなどで投稿していただけると非常に嬉しいです
よろしくお願いします
あとXなどに投稿する際にハッシュタグヤウヨルズのOSSつけてくださると見つけやすくなるのでよろしくお願いします
それでは今回もありがとうございました
ありがとうございました