Laravel 13 应用内存优化是提升性能的重要手段,本文介绍内存优化技术和最佳实践。
内存分析
内存使用监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <?php
namespace App\Services\Memory;
class MemoryMonitor { protected int $startMemory; protected array $snapshots = []; public function __construct() { $this->startMemory = memory_get_usage(true); } public function snapshot(string $label): array { $current = memory_get_usage(true); $peak = memory_get_peak_usage(true); $snapshot = [ 'label' => $label, 'current' => $current, 'current_formatted' => $this->formatBytes($current), 'peak' => $peak, 'peak_formatted' => $this->formatBytes($peak), 'delta' => $current - ($this->snapshots[count($this->snapshots) - 1]['current'] ?? $this->startMemory), 'timestamp' => microtime(true), ]; $this->snapshots[] = $snapshot; return $snapshot; } public function getSnapshots(): array { return $this->snapshots; } public function getReport(): array { return [ 'start_memory' => $this->formatBytes($this->startMemory), 'current_memory' => $this->formatBytes(memory_get_usage(true)), 'peak_memory' => $this->formatBytes(memory_get_peak_usage(true)), 'snapshots' => count($this->snapshots), 'timeline' => $this->snapshots, ]; } public function checkLimit(float $warningPercent = 0.8, float $criticalPercent = 0.95): array { $limit = $this->parseMemory(ini_get('memory_limit')); $usage = memory_get_usage(true); $percent = $usage / $limit; return [ 'limit' => $this->formatBytes($limit), 'usage' => $this->formatBytes($usage), 'percent' => round($percent * 100, 2), 'status' => $percent >= $criticalPercent ? 'critical' : ($percent >= $warningPercent ? 'warning' : 'ok'), ]; } protected function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0; return number_format($bytes / pow(1024, $power), 2) . ' ' . $units[$power]; } protected function parseMemory(string $value): int { $unit = strtoupper(substr($value, -1)); $value = (int) $value; return match ($unit) { 'G' => $value * 1024 * 1024 * 1024, 'M' => $value * 1024 * 1024, 'K' => $value * 1024, default => $value, }; } }
|
内存泄漏检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <?php
namespace App\Services\Memory;
class MemoryLeakDetector { protected array $objectCounts = []; protected array $referenceLeaks = []; public function trackObject(object $object): void { $class = get_class($object); if (!isset($this->objectCounts[$class])) { $this->objectCounts[$class] = [ 'count' => 0, 'instances' => new \WeakMap(), ]; } $this->objectCounts[$class]['count']++; $this->objectCounts[$class]['instances'][$object] = true; } public function detectLeaks(): array { $leaks = []; foreach ($this->objectCounts as $class => $data) { $alive = count($data['instances']); if ($alive > 100) { $leaks[] = [ 'class' => $class, 'total_created' => $data['count'], 'still_alive' => $alive, 'potential_leak' => true, ]; } } return $leaks; } public function getStatistics(): array { $stats = []; foreach ($this->objectCounts as $class => $data) { $stats[$class] = [ 'created' => $data['count'], 'alive' => count($data['instances']), ]; } return $stats; } }
|
数据处理优化
分块处理大数据集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <?php
namespace App\Services\Memory;
use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB;
class ChunkedProcessor { protected int $chunkSize = 1000; protected int $memoryLimit; public function __construct(int $chunkSize = 1000) { $this->chunkSize = $chunkSize; $this->memoryLimit = $this->parseMemory(ini_get('memory_limit')) * 0.8; } public function process(string $model, callable $processor): int { $processed = 0; $model::chunk($this->chunkSize, function ($items) use ($processor, &$processed) { $this->checkMemory(); foreach ($items as $item) { $processor($item); $processed++; } gc_collect_cycles(); }); return $processed; } public function processQuery($query, callable $processor): int { $processed = 0; $query->chunk($this->chunkSize, function ($items) use ($processor, &$processed) { $this->checkMemory(); foreach ($items as $item) { $processor($item); $processed++; } gc_collect_cycles(); }); return $processed; } public function processCursor(string $model, callable $processor): int { $processed = 0; foreach ($model::cursor() as $item) { $this->checkMemory(); $processor($item); $processed++; if ($processed % $this->chunkSize === 0) { gc_collect_cycles(); } } return $processed; } protected function checkMemory(): void { if (memory_get_usage(true) > $this->memoryLimit) { throw new \RuntimeException('Memory limit approaching'); } } protected function parseMemory(string $value): int { $unit = strtoupper(substr($value, -1)); $value = (int) $value; return match ($unit) { 'G' => $value * 1024 * 1024 * 1024, 'M' => $value * 1024 * 1024, 'K' => $value * 1024, default => $value, }; } }
|
流式数据处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| <?php
namespace App\Services\Memory;
use Generator;
class StreamProcessor { public function readLargeFile(string $path, int $bufferSize = 8192): Generator { $handle = fopen($path, 'r'); if (!$handle) { throw new \RuntimeException("Cannot open file: {$path}"); } try { while (!feof($handle)) { yield fgets($handle, $bufferSize); } } finally { fclose($handle); } } public function processCsv(string $path, callable $processor, bool $hasHeader = true): int { $processed = 0; $handle = fopen($path, 'r'); if (!$handle) { throw new \RuntimeException("Cannot open file: {$path}"); } try { if ($hasHeader) { fgetcsv($handle); } while (($row = fgetcsv($handle)) !== false) { $processor($row); $processed++; if ($processed % 1000 === 0) { gc_collect_cycles(); } } } finally { fclose($handle); } return $processed; } public function writeLargeFile(string $path, iterable $data, callable $formatter): int { $handle = fopen($path, 'w'); if (!$handle) { throw new \RuntimeException("Cannot open file: {$path}"); } $written = 0; try { foreach ($data as $item) { fwrite($handle, $formatter($item)); $written++; if ($written % 1000 === 0) { fflush($handle); } } } finally { fclose($handle); } return $written; } public function exportToCsv(string $path, iterable $data, array $headers = null): int { $handle = fopen($path, 'w'); if (!$handle) { throw new \RuntimeException("Cannot open file: {$path}"); } $written = 0; try { if ($headers) { fputcsv($handle, $headers); } foreach ($data as $row) { fputcsv($handle, is_array($row) ? $row : (array) $row); $written++; if ($written % 1000 === 0) { fflush($handle); gc_collect_cycles(); } } } finally { fclose($handle); } return $written; } }
|
对象池模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <?php
namespace App\Services\Memory;
interface Poolable { public function reset(): void; }
class ObjectPool { protected array $pools = []; protected array $counts = []; protected int $maxSize = 100; public function get(string $class): Poolable { if (!isset($this->pools[$class])) { $this->pools[$class] = []; $this->counts[$class] = 0; } if (!empty($this->pools[$class])) { return array_pop($this->pools[$class]); } $this->counts[$class]++; return new $class(); } public function release(Poolable $object): void { $class = get_class($object); if (!isset($this->pools[$class])) { $this->pools[$class] = []; } if (count($this->pools[$class]) < $this->maxSize) { $object->reset(); $this->pools[$class][] = $object; } } public function getStatistics(): array { $stats = []; foreach ($this->pools as $class => $pool) { $stats[$class] = [ 'pool_size' => count($pool), 'total_created' => $this->counts[$class] ?? 0, ]; } return $stats; } public function clear(string $class = null): void { if ($class) { $this->pools[$class] = []; } else { $this->pools = []; } } }
|
缓存优化
内存缓存管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| <?php
namespace App\Services\Memory;
class MemoryCache { protected array $cache = []; protected array $accessTimes = []; protected int $maxItems = 1000; protected int $maxSize; protected int $currentSize = 0; public function __construct(int $maxSizeMB = 50) { $this->maxSize = $maxSizeMB * 1024 * 1024; } public function get(string $key): mixed { if (!isset($this->cache[$key])) { return null; } $this->accessTimes[$key] = time(); return $this->cache[$key]['value']; } public function set(string $key, mixed $value, int $ttl = 3600): bool { $size = $this->estimateSize($value); if ($size > $this->maxSize) { return false; } while ($this->currentSize + $size > $this->maxSize || count($this->cache) >= $this->maxItems) { $this->evict(); } if (isset($this->cache[$key])) { $this->currentSize -= $this->cache[$key]['size']; } $this->cache[$key] = [ 'value' => $value, 'size' => $size, 'expires' => time() + $ttl, ]; $this->accessTimes[$key] = time(); $this->currentSize += $size; return true; } public function delete(string $key): bool { if (!isset($this->cache[$key])) { return false; } $this->currentSize -= $this->cache[$key]['size']; unset($this->cache[$key], $this->accessTimes[$key]); return true; } public function clear(): void { $this->cache = []; $this->accessTimes = []; $this->currentSize = 0; } public function cleanup(): int { $now = time(); $cleaned = 0; foreach ($this->cache as $key => $item) { if ($item['expires'] < $now) { $this->delete($key); $cleaned++; } } return $cleaned; } protected function evict(): void { if (empty($this->accessTimes)) { return; } asort($this->accessTimes); $keyToEvict = array_key_first($this->accessTimes); $this->delete($keyToEvict); } protected function estimateSize(mixed $value): int { if (is_string($value)) { return strlen($value); } if (is_numeric($value)) { return 16; } if (is_array($value)) { $size = 0; foreach ($value as $k => $v) { $size += strlen((string) $k) + $this->estimateSize($v); } return $size; } if (is_object($value)) { return strlen(serialize($value)); } return 0; } public function getStats(): array { return [ 'items' => count($this->cache), 'size' => $this->formatBytes($this->currentSize), 'max_size' => $this->formatBytes($this->maxSize), 'utilization' => round($this->currentSize / $this->maxSize * 100, 2) . '%', ]; } protected function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0; return number_format($bytes / pow(1024, $power), 2) . ' ' . $units[$power]; } }
|
队列内存优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <?php
namespace App\Services\Memory;
use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels;
abstract class MemoryAwareJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected int $memoryLimit; protected int $processedCount = 0; protected int $gcInterval = 100; public function __construct() { $this->memoryLimit = $this->parseMemory(ini_get('memory_limit')) * 0.8; } protected function checkMemory(): bool { return memory_get_usage(true) < $this->memoryLimit; } protected function incrementProcessed(): void { $this->processedCount++; if ($this->processedCount % $this->gcInterval === 0) { gc_collect_cycles(); } } protected function shouldRelease(): bool { return !$this->checkMemory(); } protected function parseMemory(string $value): int { $unit = strtoupper(substr($value, -1)); $value = (int) $value; return match ($unit) { 'G' => $value * 1024 * 1024 * 1024, 'M' => $value * 1024 * 1024, 'K' => $value * 1024, default => $value, }; } }
|
内存优化命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <?php
namespace App\Console\Commands;
use App\Services\Memory\MemoryMonitor; use Illuminate\Console\Command;
class MemoryReportCommand extends Command { protected $signature = 'memory:report'; protected $description = 'Generate memory usage report'; public function handle(MemoryMonitor $monitor): int { $this->info('=== Memory Report ==='); $this->newLine(); $limit = $monitor->checkLimit(); $this->table( ['Metric', 'Value'], [ ['Memory Limit', $limit['limit']], ['Current Usage', $limit['usage']], ['Utilization', $limit['percent'] . '%'], ['Status', $limit['status']], ] ); $this->newLine(); $this->info('PHP Memory Info:'); $this->table( ['Metric', 'Value'], [ ['Memory Usage', $this->formatBytes(memory_get_usage(true))], ['Memory Usage (Real)', $this->formatBytes(memory_get_usage(false))], ['Peak Memory', $this->formatBytes(memory_get_peak_usage(true))], ['Peak Memory (Real)', $this->formatBytes(memory_get_peak_usage(false))], ] ); $this->newLine(); $this->info('Garbage Collection:'); gc_collect_cycles(); $this->line('Collected: ' . gc_collect_cycles() . ' cycles'); $this->newLine(); $this->info('After GC:'); $this->line('Memory Usage: ' . $this->formatBytes(memory_get_usage(true))); $this->line('Peak Memory: ' . $this->formatBytes(memory_get_peak_usage(true))); return self::SUCCESS; } protected function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0; return number_format($bytes / pow(1024, $power), 2) . ' ' . $units[$power]; } }
|
内存优化最佳实践
查询优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <?php
namespace App\Services\Memory;
use Illuminate\Support\Facades\DB;
class QueryOptimization { public function selectOnlyNeeded(): void { User::select(['id', 'name', 'email'])->get(); User::where('active', true) ->select(['id', 'name']) ->get(); } public function useChunking(): void { User::chunk(1000, function ($users) { foreach ($users as $user) { $this->processUser($user); } }); } public function useCursor(): void { foreach (User::cursor() as $user) { $this->processUser($user); } } public function avoidNPlusOne(): void { User::with(['posts', 'comments'])->get(); } public function useLazyEagerLoading(): void { $users = User::all(); if ($needsPosts) { $users->load('posts'); } } public function processUser($user): void { } }
|
总结
Laravel 13 的内存优化需要从数据处理、对象管理、缓存策略等多个方面入手。通过分块处理、流式处理、对象池和合理的缓存策略,可以有效降低内存使用,提升应用性能。