Open6

PHP雑記

karamawanukaramawanu

裸のPHPは実は結構速いが流行り筋のフレームワークは大概重たい

裸のPHPは実は結構速い。が、フレームワーク、特に最近一般的なMVCなものを挟むと数段重くそして遅くなる。

PHP MVC Framework Showdown: 7.1 Performance – Will Bowman – Medium

No framework: 7,094 requests per second, .34M memory.
Codeigniter 3: 2,245 requests per second, .38M memory.
Lumen 5.3: 1,543 requests per second, .63M memory.
Fuel 1.8: 1,033 requests per second, .60M memory.
Symfony 3.0: 551 requests per second, 1.52M memory.
Laravel 5.3: 331 requests per second, 1.53M memory
Zend 2.5: 291 requests per second, 1.34M memory

ワースト2: PHP開発元のZendが最遅で、次が大人気のLaravelなのは笑うところ。

より多数網羅したものは kenjis/php-framework-benchmark: PHP Framework Benchmark

karamawanukaramawanu

PHP classにメンバ定義が無いのに代入できても嬉しくない場合が多いと思われる。


class foo {
  public $item_num;
}

$instance = new foo()
$instance->item_num = 10;

ありがちなプログラムの一節である。

class foo {
  public $item_num;
}

$instance = new foo()
$instance->num = 10;

これもままある。しかも動いてしまう。

厳密には、「処理を続行」する。そしてitem_numを期待してる箇所で空っぽであることにようやく発覚する。
stdclassなら判るものだが、class宣言したのだから代入制限してほしい。せめてwarningは欲しい。

OOPっぽいものを実装してはみたけど、感覚が「連想配列」のままなんだろうなあ、と忖度しないでもない。

class foo {
  public $item_num;
  protected $num;
}

$instance = new foo()
$instance->num = 10;

こうすればエラーは吐くようにはなる。が、num1への代入等は防げない。

マジックメソッドを使って、自前で判定を作ってる人は居た。
PHPのプロパティをStrictに定義する - Hack Your Design!

karamawanukaramawanu

PHP 性能比較論 array_mapは無名関数を呼ぶしかないが foreach は処理を直に書けるため前者が遅いということになりがちだが、

ありがちな比較例

まったくの別件を調べようとしてググったら、下の記事が最古で見つかった。

http://ymmtmsys.hatenablog.com/entry/2012/03/14/204536

厳密には同じ条件での比較とは言えない。

「1次元配列を加工するのにarray_map/foreachどっちで書いた方が速いか」という話ならまあこれで良い。

しかしforeachの場合は次のアドバンテージがある。

  • 関数を呼んでない
  • 配列$xsを直接潰してる。

2点目は忘れがちである。

他の記事も大体似たようなことになってる。

http://tanakahisateru.hatenablog.jp/entry/2016/05/25/232422

端的に言えば、人間が最適化しちゃってるので不公平な比較になってる。

処理内容を極力似せてみる。

この2点を踏まえて、サンプルを作り直して見ると。

<?php
$xs = array_pad(array(), 1000000, 1);

function f($x) { return $x + 100; }

$xs = array_map("f", $xs);

元との違いは、無名関数を辞めた。この1点

<?php
$xs = array_pad(array(), 1000000, 1);

function f ($x) { return $x + 100; };

foreach ($xs as &$x) {
  $x = f($x);
}

元との違いは、関数をわざわざ呼んだ。この1点。

小生の環境で測定してみる。

元記事とは環境が違うので、元のも比較のため全部載せる。

$ time php -q 2.php 

real	0m0.855s
user	0m0.829s
sys	0m0.023s
$ time php -q 3.php 

real	0m0.175s
user	0m0.161s
sys	0m0.013s
$ time php -q 2f.php 

real	0m0.530s
user	0m0.501s
sys	0m0.026s
$ time php -q 3f.php 

real	0m0.812s
user	0m0.787s
sys	0m0.020s
  • array_map版は無名関数を辞めたら2倍早く
  • forearch版は、内側で関数を呼んだら数倍遅く

何故か? array_mapは、配列を入力して、配列を出力する、という処理に特化しているため、foreachに対してこの処理が浮いてるはず。

この辺りは何処かのPHPの高速化手法にも乗ってた気がするので、納得はできる。
結果的に順序関係が逆転して、array_mapの方が2倍程度は速いとは言えそうだ。

総論として、「関数を呼んで、1次元配列の全要素を加工する処理」としてのforeach/array_mapの比較としては、大体期待どおりの結果が得られたと言える。

karamawanukaramawanu

FakerPHPのregexify関数の実装を読んでみる

偶然、fakerphpなるライブラリを知った。
今まで知らなかったのは、ランダムデータを使うタイプのテストはやらなかったため。
紹介してるページで最古はこのあたりか。

https://qiita.com/zaburo/items/4487b23543ce88ce7f0c

非常に気になったのが、regexifyという関数。
正規表現を受け付けて、それに合致する文字列を返すらしい。

まさか総当たりループとかしてないよな?とソースを眺めたらそんなことはなかった。
詳しくは下記のソースの通りなのだが。

https://github.com/FakerPHP/Faker/blob/main/src/Faker/Provider/Base.php#L493-L554

既知の正規表現パターンから、preg_replace_callback 該当する生成関数を呼んでるだけだった。

実は regexify 内部関数からも呼ばれており、生成パターンの指定方法を汎用化しようとした名残だと思われる。

https://github.com/FakerPHP/Faker/blob/main/src/Faker/Provider/Payment.php#L308-L311

regexify は v1.21.0 で追加されたことがわかる。

https://github.com/FakerPHP/Faker/commit/430c066f7797328afe292dabde608f1b784a1ab6

ちなみに v1.21.0 のタグにはそんな記述はなかった....

https://github.com/FakerPHP/Faker/releases/tag/v1.21.0

karamawanukaramawanu

PHP では rename 関数の失敗の原因が解らない件

PHPでローカルファイルをリネームするにはrenameを使うが、これはリネームに失敗してもfalseしか解らない。
オフィシャルドキュメントの通りだ。

PHP: rename - Manual

成功した場合に true を、失敗した場合に false を返します。

失敗するには例えば次のような理由が考えられる。

  • 親ディレクトリが存在しない
  • ファイル名が長すぎる

しかもPHPではWarning扱いなのだ。

故に Warning メッセージとしては存在しており、ログに出力される。例えば次の通り

[Fri Mar 03 13:32:50.692503 2023] [:error] [pid 310452:tid 140355815065280] [client 192.168.0.18:57337] PHP Warning:  rename(***,***): File name too long in ********.php on line 127, referer: ****

ログに出力してる状態であれば、この文字列をPHPのプログラムの中で受け取る機能は存在する。

PHP: error_get_last - Manual https://www.php.net/manual/en/function.error-get-last.php

戻り値は連想配列になってる。["message"]の文字列には前述のログのような文字列がそのまま入ってるので、ここからエラーの文字列を改めて判定する必要がある。

ぇぇぇ....面倒なんだが.....

他の大抵のモダンな言語実装では、例外を吐いてくれる場合が多い

python3では os.OSError例外のcodeでerrnoが採れる。 組み込み例外 — Python 3.11.2 ドキュメント

rubyではerrnoが戻るらしい。 File.rename (Ruby 3.2 リファレンスマニュアル)

nodejs fs.rename では Errorを吐くらしい File system | Node.js v19.7.0 Documentation

karamawanukaramawanu

mysqlの編集ツールといえば、特に日本では phpmyadmin 一択みたいな風潮だが、もちろん他にも幾つもある。

なかでも小生がおすすめなのは adminer である。
ほかに比べて知名度が低いのも紹介したい理由である。

https://www.adminer.org/

310kbのPHPファイル1個を設置するだけで使える

小綺麗につくられた phpmyadmin に比べて非常に淡白な意匠なため、地味な印象を受けるが、実は機能的には全く申し分ない。

サイトによれば、もともと phpminadmin を名乗っていたようだ。つまりコンセプト上は重装化した phpmyadmin にたいするアンチテーゼと言える。

ダウンロードリンクには、PHPの直接リンクがあり、実に話が早い。英語版でよければ 310kb のファイルがすべてだ。

これがphpmyadmin だと 15Mbのzipファイルに2200個のPHPファイルが入っている。取り回しの良さは一目瞭然だろう。

と言っても、実は adminer も本来は複数のファイルから構成されている。

https://github.com/vrana/adminer/

利便性のため、わざわざ結合&圧縮したものを配布しているというわけだ。

逆にこのため、サーバに設置するには注意を要する。バイナリ転送もしくはサーバのシェルで直接作業したほうが安全であろう。

実際 Visual Studio Code – コード エディター | Microsoft AzureSSH FS - Visual Studio Marketplace
でアップロードした場合、ファイルが破損する事例を目撃している。

PHP本来の専用アーカイブには phar があり、composer のCLIはこれを利用しているが、独自実装してるのは珍しいかもしれない。

ソース版を使う場合

注意点として、adminerは空のパスワードでの認証を受け付けないので注意が必要である。

実装で弾いてるだけなので修正は可能だが、配布版のPHPでは圧縮バイナリの内側なので困難である。

git のソースを直接書き換える必要がある。

https://github.com/vrana/adminer/blob/master/adminer/include/adminer.inc.php#L142-L152

その場合は git clone もしくは Download ZIP を展開したものをそのまま使えばよい。圧縮版を作る必要はまったくない。(作って使うこと自体はもちろん可能)

その場合は master/adminer/index.php が起動エントリとなる。