PHP5.4のbreak/continueについて

15日目のPHP5.4 Advent Calendarを担当します@srea2431です。
昨日は『PHP5.4でコンストラクタを呼ばずにインスタンスが作れる』という素晴らしい記事でした!
今回も前回と同様に小ネタです(´・ω:;.:…

PHP5.4からこんなのがダメになりました。
do{
do{
$j=2;
break $j; //ループを2つぬける
}while(0);
}while(0);

実行すると・・・
Fatal error: 'break' operator with non-constant operand is no longer supported in php shell code on line 4 
これは今まで通り動く
do{
do{
break 2; //ループを2つぬける
}while(0);
}while(0);
ウェブ上を見てると、『break 2;』とか『continue 2;』という表記がダメになると思っている方が意外と居られたのですが大丈夫です!
ダメになったのは、breakやcontinueの後に変数や関数など数字以外を渡した場合です。
自分はそんな使い方した事無いですが、知っている方教えて下さいm(__)m

●フレームワークで使われてるか調べてみた。
以下のフレームワークにbreak/continue $varの記述があるかgrepしたところ、1つもありませんでした。

  • ZendFramework1.11.11
  • Symfony2.0.7
  • CakePHP2.0.4
  • FuelPHPv1.1
  • CodeIgnitor2.0.3
●おわり
以上、break/continueにループ構造のレベルを指定する時は数字しか使えないよという内容でした。
明日は、@yohgakiさんお願いいたします!
まだまだPHP5.4 Advent Calendarの参加者お待ちしております!
よろしくお願いします。

php5.4で@(エラー制御演算子)はどう進化したのか?

はじめまして、PHP5.4 Advent Calendar 2011の9日目を担当する@srea2431と申します。
一日目からズラーッとPHP5.4の新機能など紹介されて来ましたが、今回はちょっと変わってphperなら誰もがこよなく愛す「@(エラー制御演算子)」についてご紹介します(違
(ホントはtraitとか[]とか注目の新機能を紹介したかったけど、ブログ書けるほどの知識も無くて消去法をしたけっか@が残りますた。許してくださいごめんなさい頑張ります。)

っというのも、、、

php5.4 alpha1の中にあった一文

遅い!遅い!遅い!と言われ続けてきたエラー制御演算子がZend Engine2のパフォーマンスチューニングを行い、@(エラー制御演算子)のパフォーマンスが改善されたみたいなんです(`・ω・´)

なので早速どう改善されたか色々調べました!

はい、結論から言いますと「php5.3.8に比べ約1.5倍早くなりました」※1

もっと見たい方は下を続けてどうぞヽ(´ー`)ノ

サーバースペック

php5.3.8で@の速さを調べる
----------------------------------------------------------
marker time index ex time perct
----------------------------------------------------------
Start 1323359828.50047400 - 0.00%
----------------------------------------------------------
php5.4 true 1323359833.33860400 4.8381299972534 34.80%
----------------------------------------------------------
php5.4 unset 1323359842.40282100 9.0642170906067 65.20%
----------------------------------------------------------
Stop 1323359842.40285900 3.7908554077148E-5 0.00%
----------------------------------------------------------
total - 13.902384996414100.00%
----------------------------------------------------------
トータルで約14秒掛かってしまいました。
php5.4で@の速さを調べる
----------------------------------------------------------
marker time index ex time perct
----------------------------------------------------------
Start 1323359762.44496900 - 0.00%
----------------------------------------------------------
php5.4 true 1323359764.74237800 2.2974090576172 26.32%
----------------------------------------------------------
php5.4 unset 1323359771.17276300 6.4303851127625 73.68%
----------------------------------------------------------
Stop 1323359771.17279300 2.9802322387695E-5 0.00%
----------------------------------------------------------
total - 8.727823972702 100.00%
----------------------------------------------------------
トータルで約9秒しか掛かりませんでした!
使ったPHPプログラム

<?php
require_once 'Benchmark/Timer.php';

$t = new Benchmark_Timer();
$t->start ();

$a = true;
for($i=0;$i<10000000;$i++){
if(@$a){
}
}

$t->setMarker( 'php5.4 true' );

unset($a);
for($i=0;$i<10000000;$i++){
if(@$a){
}
}

$t->setMarker('php5.4 unset');
$t->stop();
echo $t->getOutput ();

もっともっと見たい方は下を続けてどうぞヽ(´ー`)ノ

そもそも@をつけた所でZendEngineはどう解釈するのか
@ありなしのオペコードの比較

@あり
<?php
$a=@$b;
number of ops:  5
compiled vars: !0 = $a
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > BEGIN_SILENCE ~0
1 FETCH_R local $1 'b'
2 END_SILENCE ~0
3 ASSIGN !0, $1
3 4 > RETURN 1

branch: # 0; line: 2- 3; sop: 0; eop: 4
path #1: 0,
5行目のBEGIN_SILENCEでerror_reportingを0にして、7行目のEND_SILENCEでerror_reportingを元に戻しているようです。

@なし

<?php
$a=$b;
function name:  (null)
number of ops: 2
compiled vars: !0 = $a, !1 = $b
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > ASSIGN !0, !1
3 1 > RETURN 1

branch: # 0; line: 2- 3; sop: 0; eop: 1
path #1: 0,
とくに何もしてないようです。
※オペコードの出力にはPECLのVLDというエクステンションを使っています。
php5.4でも試してみたかったのですが、レガシーコードが削除されてVLDのコンパイルが通らなかったため確認できませんでした。でもまぁオペコードは同じだと思います。
勢いに任せてzendのソースも比較してみる。
./zend_vm_execute.h
先のオペコードで言うBEGIN_SILENCEの処理
PHP5.3.8
static int ZEND_FASTCALL ZEND_BEGIN_SILENCE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);

Z_LVAL(EX_T(opline->result.u.var).tmp_var) = EG(error_reporting);
Z_TYPE(EX_T(opline->result.u.var).tmp_var) = IS_LONG; /* shouldn't be necessary */
if (EX(old_error_reporting) == NULL) {
EX(old_error_reporting) = &EX_T(opline->result.u.var).tmp_var;
}

if (EG(error_reporting)) {
zend_alter_ini_entry_ex("error_reporting", sizeof("error_reporting"), "0", 1, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME, 1 TSRMLS_CC);
}
ZEND_VM_NEXT_OPCODE();
}
PHP5.4
static int ZEND_FASTCALL ZEND_BEGIN_SILENCE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE

SAVE_OPLINE();
Z_LVAL(EX_T(opline->result.var).tmp_var) = EG(error_reporting);
Z_TYPE(EX_T(opline->result.var).tmp_var) = IS_LONG; /* shouldn't be necessary */
if (EX(old_error_reporting) == NULL) {
EX(old_error_reporting) = &EX_T(opline->result.var).tmp_var;
}

if (EG(error_reporting)) {
do {
EG(error_reporting) = 0;
if (!EG(error_reporting_ini_entry)) {
if (UNEXPECTED(zend_hash_find(EG(ini_directives), "error_reporting", sizeof("error_reporting"), (void **) &EG(error_re
break;
}
}
if (!EG(error_reporting_ini_entry)->modified) {
if (!EG(modified_ini_directives)) {
ALLOC_HASHTABLE(EG(modified_ini_directives));
zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0);
}
if (EXPECTED(zend_hash_add(EG(modified_ini_directives), "error_reporting", sizeof("error_reporting"), &EG(error_report
EG(error_reporting_ini_entry)->orig_value = EG(error_reporting_ini_entry)->value;
EG(error_reporting_ini_entry)->orig_value_length = EG(error_reporting_ini_entry)->value_length;
EG(error_reporting_ini_entry)->orig_modifiable = EG(error_reporting_ini_entry)->modifiable;
EG(error_reporting_ini_entry)->modified = 1;
}
} else if (EG(error_reporting_ini_entry)->value != EG(error_reporting_ini_entry)->orig_value) {
efree(EG(error_reporting_ini_entry)->value);
}
EG(error_reporting_ini_entry)->value = estrndup("0", sizeof("0")-1);
EG(error_reporting_ini_entry)->value_length = sizeof("0")-1;
} while (0);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

先のオペコードで言うEND_SILENCEの処理

PHP5.3.8
static int ZEND_FASTCALL ZEND_END_SILENCE_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zval restored_error_reporting;

if (!EG(error_reporting) && Z_LVAL(EX_T(opline->op1.u.var).tmp_var) != 0) {
Z_TYPE(restored_error_reporting) = IS_LONG;
Z_LVAL(restored_error_reporting) = Z_LVAL(EX_T(opline->op1.u.var).tmp_var);
convert_to_string(&restored_error_reporting);
zend_alter_ini_entry_ex("error_reporting", sizeof("error_reporting"), Z_STRVAL(restored_error_reporting), Z_STRLEN(restored_error_repo
zendi_zval_dtor(restored_error_reporting);
}
if (EX(old_error_reporting) == &EX_T(opline->op1.u.var).tmp_var) {
EX(old_error_reporting) = NULL;
}
ZEND_VM_NEXT_OPCODE();
}
PHP5.4
static int ZEND_FASTCALL ZEND_END_SILENCE_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval restored_error_reporting;

SAVE_OPLINE();
if (!EG(error_reporting) && Z_LVAL(EX_T(opline->op1.var).tmp_var) != 0) {
Z_TYPE(restored_error_reporting) = IS_LONG;
Z_LVAL(restored_error_reporting) = Z_LVAL(EX_T(opline->op1.var).tmp_var);
EG(error_reporting) = Z_LVAL(restored_error_reporting);
convert_to_string(&restored_error_reporting);
if (EXPECTED(EG(error_reporting_ini_entry) != NULL)) {
if (EXPECTED(EG(error_reporting_ini_entry)->modified &&
EG(error_reporting_ini_entry)->value != EG(error_reporting_ini_entry)->orig_value)) {
efree(EG(error_reporting_ini_entry)->value);
}
EG(error_reporting_ini_entry)->value = Z_STRVAL(restored_error_reporting);
EG(error_reporting_ini_entry)->value_length = Z_STRLEN(restored_error_reporting);
} else {
zendi_zval_dtor(restored_error_reporting);
}
}
if (EX(old_error_reporting) == &EX_T(opline->op1.var).tmp_var) {
EX(old_error_reporting) = NULL;
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
ソースを見たところ、error_reportingを0にする処理と、0から元のerror_reportingに戻すロジックが大幅に変更されていました。

ーーーーーーーここから推測ーーーーーーー

ini設定を動的に変更するzend_alter_ini_entry_exを使わなくなり、php5.4からerror_reporting_ini_entryという変数が新たに定義されています。
error_reportingにとって共通定義されているzend_alter_ini_entry_exは余計な処理が多すぎたため使わなくなったのでしょう。
処理の内容はzend_alter_ini_entry_exに書かれている内容と似ていました。
(こんな事言ってますが、自分はC言語をあまり熟知していませんのであしからず)

ーーーーーーーここまで推測ーーーーーーー
まとめ
後半はグダグダしてしまいましたが、php5.4はphp5.3.8に比べて1.5倍も早い結果に終わりました。
これでもう@を多用しても大丈夫ですね!・・・
いや!違います!
@のせいで、エラーが出力されずバグの原因究明に時間を取られてしまう可能性があります。
いくら速度が改善されたからといって、@を多用することは止めましょう。
@使う使わないの問題ではなく、エラー制御を入れたプログラミングを心がけましょう。

@系の情報はこちらがおすすめです。御覧ください。
PHP プログラマが “@” を使うべきでない 5 つの理由

明日は、@soudai1025さんがhtmlspecialcharsの仕様変更について記事を書いて下さいます。よろしくお願いします。

※1:@の比較方法に関しては、1000万回も@を使用するソースなんてないという意見もございますが、パフォーマンス改善についての調査ですのでご理解ください。
※2:@ありと@なしの比較や@とissetの比較はそもそもこの記事のテーマとは異なるのでテストは行なっていません。
※3:phpは両バージョンともデフォルトのままコンパイルを行いました。

Bitcasaベータ版使ってみました。

Bitcasaからベータの招待メールが届いたのでメモしときます。

Bitcasaインストール直後にできたフォルダ

  • 今のところMac版のみ
  • フォルダの容量が凄い・・・
  • 上り1MB/sec前後
  • 下り1MB/secから6MB/sec
  • CPU 100%超 メモリ大食い。
  • UIがださい。
ベータ登録のメールが来たときはウハウハな気分でしたが、使ってみたら微妙でした。
もう使ってないです。
遅い遅い遅いってフィードバックしたらすぐ回答が返って来ました。

Greetings Mikan,I apologize for difficulties using the Bitcasa software. Currently application speed is limited by network connectivity. Sometimes difficult going all the way from Asia to the US. We are currently working on speed improvements for Asian customers. Thank you for reporting this issue to us.

以下Google翻訳

挨拶は、みかん
Bitcasaソフトウェアを使用して難しさのために私を深くお詫び申し上げます。
現在、アプリケーションの速度は、ネットワーク接続によって制限されます。
アジアから米国へのすべての方法を時々難しいでしょう。
我々は現在、アジアの顧客のための速度の改善に取り組んでいます。
私たちにこの問題をご報告いただきありがとうございます。

アジアだと物理的な距離の問題なども絡んでるのかもしれませんが、
これだといくら容量無制限とはいえ、1GBアップするにもヘトヘトです。
Bitcasa頑張ってください!

http://www.bitcasa.com/