前回はPCの5大装置としてのメモリ管理を学びました。
引き続きPHP・Laravelのプログラム具体例を参考にメモリについて学びたいと思います。
memory_get_usage()関数を使ってメモリを測る
$startMemory = memory_get_usage();
// 処理を実行
$endMemory = memory_get_usage();
$usedMemory = $endMemory - $startMemory;
// バイト → メガバイトへの変換
echo "使用メモリ: " . round($usedMemory / 1024 / 1024, 2) . " MB";
コントローラーでの実践的なメモリ計測
リクエスト単位でのメモリ計測は、コントローラーのビューレンダリング前に行います。
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Log;
class UserController extends Controller
{
public function index()
{
// 処理開始時間とメモリ使用量を記録
$startTime = microtime(true);
$startMemory = memory_get_usage();
// 1. データの取得や処理
$users = User::with('posts')->get();
$stats = $this->calculateUserStats($users);
// 2. ビューに渡すデータの準備
$viewData = [
'users' => $users,
'stats' => $stats,
'title' => 'ユーザー一覧'
];
// 3. ビューレンダリング前の計測
$endTime = microtime(true);
$endMemory = memory_get_usage();
// 処理時間とメモリ使用量を計算
$executionTime = round(($endTime - $startTime) * 1000, 2); // ミリ秒単位
$usedMemory = $endMemory - $startMemory;
Log::info('ユーザー一覧のパフォーマンス', [
'execution_time' => $executionTime . ' ms',
'memory_usage' => round($usedMemory / 1024 / 1024, 2) . ' MB',
'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . ' MB',
]);
// 4. ビューのレンダリング
return view('users.index', $viewData);
}
}
処理単位のメモリ使用量・処理時間目安
処理内容 | 推奨ピークメモリ | 推奨処理時間 | 注意点 |
---|---|---|---|
単純なページ表示 | 5-15MB | 50-150ms | Bladeテンプレート、軽いクエリ |
CRUD処理 | 10-30MB | 100-300ms | リレーション、Eager Loading |
ファイルアップロード | 20-50MB | 200-500ms | ファイルサイズに依存 |
バッチ処理 | 30-100MB | 1-5秒 | チャンク処理推奨 |
データエクスポート | 50-200MB | 5-30秒 | カーソル処理推奨 |
レポート生成 | 100-500MB | 10-60秒 | 複雑な集計処理 |
※ 1000ms(ミリ秒)= 1秒 です。
※ メモリ使用量はピークメモリ(memory_get_peak_usage())を基準としています
メモリ消費量を考慮したコーディング
メモリの計測は分かった!
じゃあ実際にどのようにメモリ効率を考慮してコーディングしていけばいいのか?
基本的なところを見ていきたいと思います。
1. チャンク処理の活用
大量のデータを処理する際は、チャンク処理を使用してメモリ使用量を抑えることができます:
チャンク処理とは、大量のデータを小さな塊(チャンク)に分けて処理する方法です。
一度に全てのデータをメモリに読み込むのではなく、指定した数ずつ処理することで、メモリ使用量を大幅に削減できます。
// 非推奨
$users = User::all();
foreach ($users as $user) {
// 処理
}
// 推奨:レコード1000ずつ処理していく
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// 処理
}
});
2. カーソルの使用
大量のレコードを処理する際は、カーソルを使用することでメモリ効率を改善できます。
カーソルとは、データベースからレコードを1つずつ取得して処理する方法です。チャンク処理と異なり、一度に1つのレコードのみをメモリに保持するため、メモリ使用量を最小限に抑えることができます。特に大量のレコードを順次処理する場合に効果的です。
foreach (User::cursor() as $user) {
// 処理
}
チャンク vs カーソルの違い・使い分け
- チャンク処理: 指定した件数ずつ取得 → メモリ使用量は多いが処理速度が速い
- カーソル処理: 1件ずつ取得 → メモリ使用量は最小だが処理速度は遅い
3. クエリの最適化
取得するクエリ根本を考えることも大切です。超基本ですが、
必要なカラムのみを取得することで、メモリ使用量を削減できます:
// 非推奨
$users = User::all();
// 推奨
$users = User::select('id', 'name', 'email')->get();
N+1問題の回避
N+1問題は、メモリ使用量を大幅に増加させる原因の一つです。リレーションを含むデータを取得する際は、Eager Loadingを使用しましょう
- Lazy Loading: リレーションにアクセスした時点でクエリ実行 → N+1問題の原因
- Eager Loading: 事前に関連データを取得 → メモリ効率的
// 非推奨(N+1問題)
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count(); // Lazy Loading: 各ユーザーごとにクエリが実行される
}
// 推奨(Eager Loading)
$users = User::with('posts')->get();
foreach ($users as $user) {
echo $user->posts->count(); // 事前に取得済み(Lazy Loadingなし)
}