第21章 パフォーマンス

コードの最適化

ループ内での不要な計算

悪い例:ループ内で同じ計算が何度も実行される例です。

// 非効率なコード
for ($i = 0; $i < count($array); $i++) {
  // 何かの処理
}

改善点:ループ外で計算して、変数に保持することで最適化します。

// 効率的なコード
$length = count($array); // ループ外で計算
for ($i = 0; $i < $length; $i++) {
  // 何かの処理
}


不必要な変数の割り当て

悪い例:不必要な変数を作り、リソースを消費している例です。

// 非効率なコード
function getFullName($firstName, $lastName) {
  $fullName = $firstName . ' ' . $lastName;
  return $fullName;
}

改善点:直接結果を返すことで、無駄な変数の割り当てを避けます。

// 効率的なコード
function getFullName($firstName, $lastName) {
  return $firstName . ' ' . $lastName;
}


関数呼び出しの過剰

悪い例:ループ内で同じ関数が何度も呼び出される例です。

// 非効率なコード
for ($i = 0; $i < 1000; $i++) {
  doSomethingExpensive();
}

改善点:必要なく繰り返し同じ関数を呼び出す場合、結果をキャッシュするなどして最適化します。

// 効率的なコード
$result = doSomethingExpensive(); // 一度だけ呼び出す
for ($i = 0; $i < 1000; $i++) {
  // $resultを使った処理
}


グローバル変数の過剰な使用

悪い例:グローバル変数は使用が簡単ですが、多用するとコードの読みやすさと保守性が下がります。

// 非効率なコード
$globalVar = 10;

function doSomething() {
  global $globalVar;
  // 何かの処理
}

改善点:関数の引数を通じてデータを渡す方が、コードの流れが追いやすく、テストも書きやすくなります。

// 効率的なコード
function doSomething($param) {
  // 何かの処理
}

$var = 10;
doSomething($var);


SQLクエリの最適化

悪い例:ループ内で同じクエリを何度も実行する場合があります。

// 非効率なコード
foreach ($userIds as $userId) {
  $query = "SELECT * FROM users WHERE id = $userId";
  // クエリの実行
}

改善点:クエリを一度にまとめて実行する方が効率的です。

// 効率的なコード
$userIdsStr = implode(',', $userIds);
$query = "SELECT * FROM users WHERE id IN ($userIdsStr)";
// クエリの実行


使用後の変数の解放

悪い例:大量のデータを扱う際、変数を解放しないとメモリの消費が増えます。

// 非効率なコード
$largeArray = getLargeArray();
// 何かの処理
// その後$largeArrayは不要

改善点:不要になった変数は解放します。

// 効率的なコード
$largeArray = getLargeArray();
// 何かの処理
unset($largeArray); // 不要になった変数の解放


パフォーマンスを計測する関数

実行時間を計測する関数

microtime関数を使用すると、マイクロ秒単位で現在のUnixタイムスタンプを取得できます。これを用いると、コードの特定のセクションがどれだけの時間を要するかを測定することができます。

$start = microtime(true);

// 何らかの処理

$end = microtime(true);
$execution_time = ($end - $start);

echo "実行時間: " . $execution_time . " 秒";


メモリ使用量を取得する関数

memory_get_usage関数は、スクリプトが現在使用しているメモリの量をバイト単位で返します。

echo "初期のメモリ使用量: " . memory_get_usage() . " バイト\n";

// 何らかのメモリを消費する処理
$array = range(1, 1000000);

echo "処理後のメモリ使用量: " . memory_get_usage() . " バイト\n";


最大メモリ使用量を取得する関数

memory_get_peak_usage関数は、スクリプトの実行中に確保されたメモリの最大量をバイト単位で返します。

echo "初期のメモリ使用量: " . memory_get_usage() . " バイト\n";

// 何らかのメモリを消費する処理
$array = range(1, 1000000);

echo "処理後のメモリ使用量: " . memory_get_usage() . " バイト\n";
echo "ピークメモリ使用量: " . memory_get_peak_usage() . " バイト\n";


OPcache

OPcacheとは何か?

PHPスクリプトは、リクエストごとにコンパイラによってバイトコードにコンパイルされ、それが実行されます。OPcacheはこのバイトコードをキャッシュしておき、同じスクリプトが再度リクエストされた際にはコンパイルの工程を省略できるようにします。これによって、アプリケーションのレスポンス時間が大幅に減少し、全体的なパフォーマンスが向上します。


使い方

PHP 5.5以降では、OPcacheはデフォルトでビルドされるようになっているので、通常はphp.iniファイルで有効化するだけで使用することができます。

php.iniファイルで以下の設定を行います。

opcache.enable=1

オプションには下記のようなものがあります。

opcache.enable=1  ; OPcacheを有効にする
opcache.memory_consumption=128  ; 128MBをOPcacheの使用メモリとして設定
opcache.interned_strings_buffer=8  ; 8MBをインターンド文字列のバッファとして設定
opcache.max_accelerated_files=4000  ; 最大4000のスクリプトをキャッシュ
opcache.revalidate_freq=60  ; 60秒ごとにタイムスタンプを再確認
opcache.validate_timestamps=1  ; タイムスタンプの確認を有効にする
opcache.save_comments=1  ; コメント情報をキャッシュに保存
opcache.consistency_checks=0  ; 一貫性のチェックを無効にする
opcache.optimization_level=0xFFFFFFFF  ; すべての最適化を有効にする
; opcache.preferred_memory_model=  ; 通常はコメントアウトまたは空のまま
opcache.use_cwd=1  ; カレントディレクトリをキャッシュキーの一部として使用
; opcache.blacklist_filename=/path/to/blacklist.txt  ; 必要な場合に設定
opcache.max_file_size=0  ; すべてのファイルサイズをキャッシュする


OPcacheの監視と管理

OPcacheの状態やキャッシュ統計を取得するための関数も提供されています。例えば、opcache_get_status()opcache_get_configuration()などの関数が使えます。また、OPcacheをより効果的に管理するためのサードパーティ製のツールも存在します。


注意点

開発環境では注意が必要: ファイルが変更されてもすぐに反映されないため、開発中はOPcacheを無効にするか、適切に設定する必要があります。

キャッシュの設定: サイズや再検証の頻度などのキャッシュの設定が不適切だと、逆にパフォーマンスが下がる場合もあるので、注意が必要です。


練習問題1.

次の関数は配列の要素の合計を返す関数です。この関数のパフォーマンスを向上させてください。

function slowArraySum($arr) {
    $sum = 0;
    for ($i = 0; $i < count($arr); $i++) {
        $sum += $arr[$i];
    }
    return $sum;
}


練習問題2.

以下の関数は配列に重複があるかどうかをチェックする関数です。より効率的な方法で重複をチェックする関数に書き換えてください。

function hasDuplicates($arr) {
    for ($i = 0; $i < count($arr); $i++) {
        for ($j = $i + 1; $j < count($arr); $j++) {
            if ($arr[$i] == $arr[$j]) {
                return true;
            }
        }
    }
    return false;
}


練習問題3.

文字列を逆転させる以下の関数のパフォーマンスを向上させてください。

function slowReverseString($str) {
    $reversed = '';
    for ($i = strlen($str) - 1; $i >= 0; $i--) {
        $reversed .= $str[$i];
    }
    return $reversed;
}