web-dev-qa-db-ja.com

PHP exec()のパフォーマンス

次のPHPコードは、約3.5秒のランタイムを返します(複数回測定され、平均化されます)。

_$starttime = microtime(true);
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;
_

Sshターミナルで同じコマンドを実行すると、実行時間は約0.6秒に短縮されます(コマンドラインツールtimeで測定)。

Imagemagickライブラリのバージョンは

_Version: ImageMagick 6.7.0-10 2012-12-18 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2011 ImageMagick Studio LLC
Features: OpenMP
_

この時差の理由は何でしょうか?

ここスタックオーバーフローに関する同様の質問に対する1つの答えは、オーバーヘッドはWebサーバーがスレッド/シェルを開始しなければならないことに起因するというものでした。これが本当に理由でしょうか?スレッドは軽量で、開始/終了するのにそれほど時間はかからないと思いました。

execを呼び出す前に、imagemagickが使用するスレッドの数を(これはOpenMPのバグだったので、 参照exec('env MAGICK_THREAD_LIMIT=1');で1に設定しました。 PHPからのランタイムは、_MAGICK_THREAD_LIMIT_に設定した値に関係なく、あまり変化しません。とにかく、このバージョンのOpenMPにはバグがないようです。コマンドラインの実行は問題ありません。

上記のコマンドの実行時間を改善する方法についての提案をいただければ幸いです。

手伝ってくれてありがとうございます。

21
Philipp

キーボードまたはsshを介してUnixマシンにログインすると、シェルの新しいインスタンスが作成されます。シェルは通常、_/bin/sh_または_/bin/bash_のようなものです。シェルを使用すると、コマンドを実行できます。

exec()を使用すると、シェルの新しいインスタンスも作成されます。そのインスタンスは、送信されたコマンドを実行してから終了します。

シェルコマンドの新しいインスタンスを作成すると、独自の環境変数があります。したがって、これを行う場合:

_exec('env MAGICK_THREAD_LIMIT=1');
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');
_

次に、2つのシェルを作成すると、最初のシェルの設定が2番目のシェルに到達することはありません。 2番目のシェルで環境変数を取得するには、次のようなものが必要です。

_exec('env MAGICK_THREAD_LIMIT=1; /usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');
_

さて、シェル自体が問題である可能性があると思われる場合は、シェルの作成に時間がかかりすぎるため、次のことを試してみてくださいknowほとんど時間がかかりません。

_$starttime = microtime(true);
exec('echo hi');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;
_

その時点で、シェルのインスタンス化を高速化する方法を見つけようとする必要があります。

お役に立てれば!

14
Gustav Bertram

私は56年以上コンピューターをプログラミングしてきましたが、このようなバグに遭遇したのはこれが初めてです。そのため、Perlからphpを介してexecプログラムを実行する場合、Perlプログラムを直接実行する場合と比較して、実行速度が7倍悪いことを理解するために1週間近く費やしました。コマンドライン。この取り組みの一環として、私はこの問題がWeb上で提起されたすべての時間についても詳しく調べました。これが私が見つけたものです:

(1)これは、2002年に最初に報告されたバグであり、その後11年間は修正されていません。

(2)このバグは、Apachephpと相互作用する方法に関連しているため、これらの組織は両方とももう一方にお金を渡します。

(3)バグは、exec、システム、またはその他のいずれでも同じです。

(4)バグは、実行されたプログラムがPerlexeなどであるかどうかに依存しません。

(5)バグはUNIXとWindowsで同じです。

(6)このバグは、imagemagickまたは一般的な画像とは何の関係もありません。まったく異なる設定でバグに遭遇しました。

(7)このバグは、フォーク、シェル、bashなどの起動時間とは何の関係もありません。

(8)Apacheサービスの所有者を変更してもバグは修正されません。

(9)よくわかりませんが、サブルーチンを呼び出す際のオーバーヘッドの大幅な増加に関連していると思います。

この問題が発生したとき、40秒で実行されるPerlプログラムがありましたが、execを介して304秒かかりました。私の究極の解決策は、プログラムを最適化して、直接0.5秒またはexecを介して3.5秒で実行されるようにする方法を見つけることでした。だから私はその問題を解決しなかった。

10
user2684428

@Philippは、SSHがあり、サーバーがexec()へのアクセスを許可しているため、マシンへの完全なルートアクセスも持っていると想定します。

単一ファイル処理に推奨

マシンへのルートアクセス権があるということは、_/etc/php5/php.ini_メモリ制限設定を変更できることを意味します。

_/etc/php5/php.ini_に直接アクセスしなくても、プロジェクトディレクトリに新しい_php.ini_ファイルを作成することで、サーバーが_php.ini_ディレクティブのオーバーライドをサポートしているかどうかを確認できます。

オーバーライドが許可されていない場合でも、AllowOverrideAllの場合は、メモリ設定を_.htaccess_から変更できます。

メモリ制限を変更するさらに別の方法は、ini_set('memory_limit', 256);を使用してPHPランタイム中に設定することです。

バッチファイル処理に推奨

exec()を介して変換を実行することの唯一の良い点は、exec()から結果を取得して、非同期で実行できるようにする予定がない場合です。

_exec('convert --your-convert-options > /dev/null 2>/dev/null &');
_

上記のアプローチは、多くのファイルをバッチ処理しようとしている場合、ファイルの処理が完了するのを待ちたくない場合、および各ファイルが処理されたことを確認する必要がない場合に通常役立ちます。

パフォーマンスノート

上記のコードを使用して単一のファイルを処理するためにexecasyncで実行すると、PHP内でGd/Imagickを使用するよりもプロセッサ時間とメモリが多くなります。時間/メモリは、PHPプロセス(訪問者にサイトの移動が速くなるように感じさせる)に影響を与えない別のプロセスによって使用されますが、メモリ消費が存在し、多くの処理に関してはカウントされる接続。

5
Mihai Stancu

これはPHPバグではなく、Apache/Nginxやその他のWebサーバーとは何の関係もありません。

私は最近同じ問題を抱えていて、exec()の実装をチェックするためにPHPソースコードを調べました。

基本的に、PHPのexec()はLibcのpopen()関数を呼び出します。

ここでの違反者はCのpopen()で、非常に遅いようです。 「cpopenslow」をグーグルですばやく検索すると、あなたのような質問がたくさん表示されます。

また、このパフォーマンスの問題を克服するために、誰かがCでpopen_noshell()という関数を実装していることもわかりました。

https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/

これは、速度の違いとpopen()およびpopen_noshell()の違いを示すスクリーンショットです。

Speed difference

PHPのexec()は、通常のpopen()を使用します。これは上のスクリーンショットの右側にあります。ご覧のとおり、Cのpopen()を実行するときにシステムが使用するCPUは非常に高くなっています。

この問題には2つの解決策があります。

  1. popen_noshell を実装するPHP拡張機能を作成します
  2. PHPチームから、新しい関数のセットpopen_noshell()、exec_noshell()などを作成するように要求します...これは起こりそうもないと思います...

追記:

これを検索しているときに、Cのものと同じ名前のPHP関数を発見しました:popen()

Pclose(popen( 'your command'、 'r'));を使用して、外部コマンドを非同期で実行できるので興味深いです。

これは基本的にexec( 'your command&');と同じ効果があります。

4
Abu Junayd

execを呼び出すと、phpはスレッドを作成せず、新しい子プロセスを作成します。新しいプロセスの作成は大きなオーバーヘッドです。

ただし、sshに接続するときは、実行するコマンドを渡すだけです。あなたはそのプログラムの所有者ではないので、あなたが接続したユーザーとして実行されます。 execの場合、PHPを実行するユーザー。

2
Shiplu Mokaddim

私はこの問題を経験しました。コマンドラインから実行すると約.025秒かかるグラフィック処理コマンドは、PHPのexec()から呼び出すと約.3秒かかりました。多くの調査の結果、ほとんどの人がこれがApacheまたはPHPの問題であると信じているようです。次に、PHPを完全にバイパスして、CGIスクリプトを介してコマンドを実行しようとしましたが、同じ結果が得られました。

したがって、問題はApacheにあるはずなので、lighttpdをインストールしても、同じ結果が得られました。

いくつかの考えと実験の後、私はこれがプロセッサの優先順位の問題であるに違いないことに気づきました。したがって、コマンドをコマンドラインと同じ速度で実行する場合は、次のように実行する必要があります。

exec('echo "password" | Sudo -S Nice -n -20 command')

注意:これにはあらゆる種類のセキュリティ上の反対意見があることを私は知っています。私はあなたがしなければならないのはあなたの命令の前にニースを加えることだけであるという答えに単に焦点を合わせたかっただけです。

1
Josef Fort