PHPのパフォーマンスチューニング(メモ化)


はじめに

Laravelはよくパフォーマンスが悪いと言われますが、コードの書き方やLaravelの使い方次第で大幅にパフォーマンスは改善することが可能です。
本日はパフォーマンスチューニングの手法の一つであるメモ化について記事にしたいと思います。

動作確認環境

  • PHP 8.0
  • Laravel 8.0

※下位環境でも動作する場合がございます

パフォーマンスについて

一番遅い:データベースから値を取得
遅い:外部キャッシュ(Redis、Memcacheなど)から値を取得
速い:データキャッシュとしてのapcuから値を取得
一番速い:メモ化して値を取得

保存箇所へのアクセスが遠いほど、パフォーマンス劣化が発生します。

メモ化とは

メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法

Wikipediaには上記のようにあります。
噛み砕くと、処理の返り値を変数に保存しておき、それを再利用することで同じ計算を省略して処理速度を向上させる手法です。

Laravelでメモ化が使われている箇所

Laravelでは、Illuminate\Support\Strなどで実際に使われています。
以下は、引数で渡された値を先頭大文字の英字(通称アッパーキャメルケース)に変換してくれるメソッドです。
一度目の呼び出しと同じ引数が渡された場合は、すでにメモ化しているので、変換処理をはさまずに値が返ります。

/**
 * The cache of studly-cased words.
 *
 * @var array
 */
protected static $studlyCache = [];

/**
 * Convert a value to studly caps case.
 *
 * @param  string  $value
 * @return string
 */
public static function studly($value)
{
    $key = $value;

    if (isset(static::$studlyCache[$key])) {
        return static::$studlyCache[$key];
    }

    $value = ucwords(str_replace(['-', '_'], ' ', $value));

    return static::$studlyCache[$key] = str_replace(' ', '', $value);
}

クロージャーを使ったメモ化

Laravelの\Illuminate\Cache\Repository::rememberをメモ化用に改変

/**
 * @var array
 */
protected $localCache = [];

/**
 * Memoization sample
 *
 * @param  string  $cacheKey
 * @return mixed
 */
public function localCache(string $cacheKey)
{
    return $this->rememberLocal($cacheKey, function () {
        // メモ化したい処理
        // 引数のcacheKeyが同じであれば一度目しか重い処理は通りません
    });
}

/**
 * Get an item from the cache, or execute the given Closure and store the result.
 *
 * @param  string  $key
 * @param  \Closure  $callback
 * @return mixed
 */
private function rememberLocal(string $key, Closure $callback)
{
    $value = $this->get($key);

    if ($value !== null) {
        return $value;
    }

    $this->put($key, $value = $callback());

    return $value;
}

/**
 * Retrieve an item from the local cache by key.
 *
 * @param  string  $key
 * @return mixed
 */
private function get(string $key)
{
    return $this->localCache[$key] ?? null;
}

/**
 * Store an item in the local cache.
 *
 * @param  string  $key
 * @param  mixed  $value
 */
private function put(string $key, $value): void
{
    $this->localCache[$key] = $value;
}

編集後記

パフォーマンスチューニングについて勉強した時に一番最初に学んだメモ化について記事にしました。

どこまでパフォーマンスチューニングするかはケースバイケースだとは思いますが、私の場合DBへのアクセスするのは最終手段という中で育ってきたので、メモ化は積極的に使うようにしています。
1回の通信で同じ重い処理を2度以上する可能性がある場合にはまずはメモ化の検討をオススメします。

Laravelのパフォーマンスチューニングに関する記事