にゃあ

register_shutdown_function()はFatal後でも動く

register_shutdown_function()で登録したコールバック関数が、実行時にFatalになった時でも動くのか試してみた。

ケース1: メモリを使い切った場合

メモリを使い切るまで、配列を足し続けるコードを書いた。メモリオーバのエラーで落ちても、コールバック関数は呼ばれた。

<?php
error_reporting(-1);
ini_set('display_errors', 1);

register_shutdown_function('unexpected_shutdown');

function unexpected_shutdown()
{
	echo '予期しないシャットダウンです。';
}

while ( true ) // メモリを使い切るまでループ
{
	$hugeData[] = array();
}

// 結果:
// Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 40 bytes) in /Applications/MAMP/htdocs/php/register_shutdown_function/case1.php on line 15
// 予期しないシャットダウンです。

ただ、これもコールバック関数を実行するのに十分なメモリがあるときだけ成功するみたい。コールバック関数内でメモリを大量消費すると、当然こけちゃう。コールバック関数で、メモリを沢山必要とする場合は、unset()できるものはしてメモリ領域を確保しないとダメっぽい。

<?php

error_reporting(-1);
ini_set('display_errors', 1);

register_shutdown_function('unexpected_shutdown');

function unexpected_shutdown()
{
	$dummy = range(0, 1000); // ここでメモリオーバ
	echo '予期しないシャットダウンです。';
}

while ( true )
{
	$hugeData[] = array();
}

// 結果:
// Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 40 bytes) in /Applications/MAMP/htdocs/php/register_shutdown_function/case1.1.php on line 16

ケース2: 構文エラーの外部ファイルをincludeした場合

この場合もコールバック関数が動いた。

<?php

error_reporting(-1);
ini_set('display_errors', 1);

register_shutdown_function('unexpected_shutdown');

function unexpected_shutdown()
{
	echo '予期しないシャットダウンです。';
}

require 'parse_error.php'; // syntax error のファイル

echo 'OK'; // これを表示する前に落ちる

// 結果: 
// Parse error: syntax error, unexpected T_STRING in /Applications/MAMP/htdocs/php/register_shutdown_function/parse_error.php on line 3
// 予期しないシャットダウンです。

register_shutdown_function()を使えば、せめてHTTP 503エラーくらい出せるかも。




PHPのunset()とメモリの開放を調べてみた

メモリの使用量を調べるに当たって、検査用関数を作ってみた。初期メモリ使用量は無視して、純粋に変数で使われた容量を検出するもの。$initialMemoryUseですでに20バイトくらい使っている、というのは誤差の範囲で^^

function dumpMemory()
{
	static $initialMemoryUse = null;

	if ( $initialMemoryUse === null )
	{
		$initialMemoryUse = memory_get_usage();
	}

	var_dump(number_format(memory_get_usage() - $initialMemoryUse));
}

ケース1

dumpMemory(); // string(1) "0"

$hoge = array();

dumpMemory(); // string(3) "184"

for ( $i = 1; $i < 10000; $i++ )
{
	$hoge[$i] = null;
}

dumpMemory(); // string(7) "785,720"

for ( $i = 1; $i < 10000; $i++ )
{
	unset($hoge[$i]);
}

dumpMemory(); // string(6) "65,764"

んー、なんかものすごいオーバヘッドがあるんだけど。

ケース2

dumpMemory(); // string(1) "0"

$hoge = array();

dumpMemory(); // string(3) "184"

for ( $i = 1; $i < 10000; $i++ )
{
	$hoge[$i] = null;
	unset($hoge[$i]);
}

dumpMemory(); // string(3) "260"

ケース1の変形版。unsetのタイミングをforの中にしてみたら、かなりオーバヘッドが減った。理由はわかんない。

ケース3

dumpMemory(); // string(1) "0"

$hoge = array();

dumpMemory(); // string(3) "184"

for ( $i = 1; $i < 10000; $i++ )
{
	$hoge[$i] = null;
}

dumpMemory(); // string(7) "785,732"

unset($hoge);

dumpMemory(); // string(2) "96"

これもまたケース1の変形版。forでunset()しないで、配列まるごとunset()するようにしてみた。ケース2よりもオーバヘッドはひどくない。

ケース4

function scopeTest()
{
	$array = range(1, 10000);
	dumpMemory();
	// ここで$arrayはスコープを失う
}

dumpMemory(); // string(1) "0"

scopeTest(); // string(7) "785,700"

dumpMemory(); // string(2) "20"

今度はスコープでの実験。スコープが切れた変数はメモリから解放されると聞いていたけど、実際そのとおりみたい。

ケース5

class TestClass
{
	public static function test()
	{
		$array = range(1, 10000);
		dumpMemory();
		// ここでスコープを失う
	}
}

dumpMemory(); // string(1) "0"

TestClass::test(); // string(7) "785,680"

dumpMemory(); // string(2) "20"

ケース4をクラスにしてみただけ。静的関数でもスコープが切れた段階で、メモリが開放されていた。

結論みたいなもの

  • スコープ(寿命)が短い変数は特にunset()する必要はない。
  • グローバル(=寿命が長い)は変数は、うまいタイミングでunset()するか、できるだけスコープを短くしたほうがいい。
  • unset()の仕方によってはオーバヘッドがひどい。

ApacheからPHPを実行するくらいなら、プロセス自体の寿命も短いからメモリなんてそうそう気にならないけど、PHPでバッチ処理をしようとするとメモリ管理も大変になるんだよなあ。扱うデータ量が全然違ってくるし、プロセスの寿命も長いし。メモリを気にするならPHPなんてやめちまえ、ってこと?




ob_start()後にthrow new Excpetionすると出力バッファが解除される?

検証1

try
{
	ob_start();
	echo "これは見えちゃだめ!";
	throw new Exception();
}
catch ( Exception $e )
{
}

echo "こっちが先。";

結果1

これは見えちゃだめ!こっちが先。

検証2

try
{
	ob_start();
	echo "これは見えちゃだめ!";
	ob_start();
	echo "これは見えちゃだめだってば!";
	throw new Exception();
}
catch ( Exception $e )
{
}

echo "こっちが先。";

結果2

これは見えちゃだめ!これは見えちゃだめだってば!こっちが先。

検証3

class MyException extends Exception
{
	public function __construct()
	{
		echo "例外のコンストラクタだよ。";
	}
}

try
{
	ob_start();
	echo "これは見えちゃだめ!";
	throw new MyException();
}
catch ( MyException $e )
{
}

echo "こっちが先。";

結果3

これは見えちゃだめ!例外のコンストラクタだよ。こっちが先。

検証4

try
{
	ob_start();
	echo "これは見えちゃだめ!";
	throw new Exception();
}
catch ( Exception $e )
{
	$content = ob_get_clean();
}

echo "こっちが先。";
echo $content;

結果4

こっちが先。これは見えちゃだめ!

まとめ

  • 例外によって出力バッファが解かれるというより、プロセス終了時に開放される模様
  • 例外もキャッチしたときに、ob_get_clean()などを施せば、一応出力バッファを捕獲することはできる。
  • 捕獲はできるけれども、プロジェクトが大規模になってくると捕獲が困難になりそう。何かいい方法ないかな…?



register_shutdown_functionいりこ状にしてもOK

register_shutdown_function()関数は、PHPのプロセス終了直前にコールバックする関数を登録するものです。用途によっては便利な機能なのです。この関数についてふと疑問が。「register_shutdown_function()はいりこ状にできるのか」というものです。気になったのでやってみました。

<?php
error_reporting(-1);
ini_set('display_errors', 1);

register_shutdown_function('shutdown');

function shutdown()
{
	var_dump(__FUNCTION__);
	register_shutdown_function('shutdown_recursive');
}

function shutdown_recursive()
{
	var_dump(__FUNCTION__);
	register_shutdown_function('shutdown_recursive_recursive');
}

function shutdown_recursive_recursive()
{
	var_dump(__FUNCTION__);
}

実行結果

string(8) "shutdown"
string(18) "shutdown_recursive"
string(28) "shutdown_recursive_recursive"

エラーも出ずに、あっさりと出来てしまいました。まずこんな実装はないと思いますが、「register_shutdown_function()はいりこ状にできる」というのは覚えておくといいかもしれません。




PHPのNULLはあらゆる可能性を秘めていた...!

<?php
error_reporting(-1);
ini_set('display_errors', 1);

$null = null;
$null += 10;
var_dump($null); // int(10)

$null = null;
$null .= 'hoge';
var_dump($null); // string(4) "hoge"

$null = null;
$null['hoge'] = 1;
var_dump($null);

/*
array(1) {
  ["hoge"]=>
  int(1)
}
*/

$null = null;
$null->hoge = 1;
var_dump($null);

/*
Strict Standards: Creating default object from empty value
object(stdClass)#1 (1) {
  ["hoge"]=>
  int(1)
}
*/

NULLは数値や文字列はもちろん、配列にもオブジェクト(stdClass)にもなんだってなれます。さすが、型がゆるい言語。





Author

Submenu

Recent Entries

XOOPS Cube Dev Ring

氷川 XOOPS Module 開発室

Recent Comments

Recent Trackbacks