Meydan okuma - Performans optimizasyonu
OPcache, benchmark ve eager loading'le optimize edilmiş bir rapor servisi yazarsın.
Performans: üç katman
Bu derste performansı üç katmanda iyileştirirsin. OPcache bytecode'u bellekte tutar; opcache_get_status ile okuruz. Benchmark hrtime(true) ile nanosaniye süre, memory_get_usage(true) ile bellek ölçer. Eager loading ise N+1 sorgu sorununu tek JOIN ile çözer.
N+1 sorunu şudur: bir liste için bir sorgu, ardından her satırın ilişkisi için ayrı sorgu yapılırsa N+1 sorgu çalışır. JOIN ile tek sorguda tüm ilişkileri çekmek eager loadingtir ve özellikle büyük listelerde büyük kazanç sağlar.
Cache ve benchmark birleşimi
Cache::get(string $key, callable $loader) deseni önbelleği zarif kılar: anahtar yoksa loader çağrılır ve sonuç saklanır; varsa doğrudan döndürülür. Aynı anahtara ikinci çağrı loader'ı tekrar çalıştırmaz; === ile aynı örneği döndürdüğünü kanıtlarsın.
benchmark(callable $fn): array fonksiyonu hrtime(true) ile süre, memory_get_usage(true) ile bellek ölçer ve result, ms, bytes anahtarlarıyla dizi döndürür. Tipik kullanım: benchmark(fn() => $s->eagerReport()). Sonuç deterministik olduğundan yalnızca is_int kontrollerini yazdırırız.
Uygulama Cache + benchmark + ReportService yaz
Başlangıç kodunda ETL ve ReflectionSerializer hazır. Şimdi üç parça ekle. Cache sınıfı: özel array $store = []; get(string $key, callable $loader) anahtar yoksa loader'ı çağırıp saklar, varsa döndürür.
benchmark(callable $fn): array fonksiyonu hrtime(true) ile başlangıç ve bitiş alır, farkı 1000000'a bölüp (int) ile milisaniyeye çevirir; memory_get_usage(true) ile bellek ölçer; result, ms, bytes anahtarlı dizi döndürür. ReportService PDO alır; products ve categories tablolarını oluşturur, bir kategori ve iki ürün ekler; eagerReport(): array tek JOIN sorgusuyla tüm satırları döndürür (N+1 yok). Demo sqlite::memory: ile çalışır.
Örnek çözümü göster
get(string $key, callable $loader) anahtar yoksa loader çağırır ve saklar. benchmark(callable $fn): array fonksiyonu hrtime(true) ile süreyi, memory_get_usage(true) ile bellek kullanımını ölçer; result, ms, bytes anahtarlı dizi döner. ReportService PDO alır, products ve categories tablolarını oluşturur, iki ürün ekler; eagerReport() tek JOIN sorgusuyla N+1 sorununu önler.
Örnek çözüm:
<?php
// ETL + Reflection + Performans: Cache + benchmark + ReportService yaz
function readRows(array $rows): Generator {
foreach ($rows as $row) {
yield $row;
}
}
function transform(iterable $rows, callable $fn): Generator {
foreach ($rows as $row) {
yield $fn($row);
}
}
function pipeline(array $rows, callable $fn): Generator {
yield from transform(readRows($rows), $fn);
}
function loadAll(iterable $rows): array {
$out = [];
foreach ($rows as $row) {
$out[] = $row;
}
return $out;
}
#[Attribute]
class Field {
public function __construct(public string $name = "") {}
}
class Product {
public function __construct(
#[Field("id")] public int $id,
#[Field("name")] public string $name,
) {}
}
class ReflectionSerializer {
public function serialize(object $obj): array {
$rc = new ReflectionClass($obj);
$out = [];
foreach ($rc->getProperties() as $prop) {
$attrs = $prop->getAttributes(Field::class);
if (empty($attrs)) continue;
$field = $attrs[0]->newInstance();
$key = $field->name !== "" ? $field->name : $prop->getName();
$out[$key] = $prop->getValue($obj);
}
return $out;
}
}
class Cache {
private array $store = [];
public function get(string $key, callable $loader) {
if (!isset($this->store[$key])) {
$this->store[$key] = $loader();
}
return $this->store[$key];
}
}
function benchmark(callable $fn): array {
$start = hrtime(true);
$result = $fn();
$elapsed = (int)((hrtime(true) - $start) / 1000000);
$mem = memory_get_usage(true);
return ["result" => $result, "ms" => $elapsed, "bytes" => $mem];
}
class ReportService {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
$this->pdo->exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, category_id INTEGER)");
$this->pdo->exec("CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT)");
$this->pdo->prepare("INSERT INTO categories (name) VALUES (?)")->execute(["kirtasiye"]);
$this->pdo->prepare("INSERT INTO products (name, category_id) VALUES (?, ?)")->execute(["kale", 1]);
$this->pdo->prepare("INSERT INTO products (name, category_id) VALUES (?, ?)")->execute(["defter", 1]);
}
public function eagerReport(): array {
$sql = "SELECT p.name AS product, c.name AS category FROM products p JOIN categories c ON p.category_id = c.id ORDER BY p.id";
return $this->pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
}
// Demo:
$pdo = new PDO("sqlite::memory:");
$s = new ReportService($pdo);
$cache = new Cache();
$rows = $cache->get("report", fn() => $s->eagerReport());
$second = $cache->get("report", fn() => $s->eagerReport());
echo "satır: " . count($rows) . "\n";
echo "önbellekli: " . ($rows === $second ? "1" : "0") . "\n";
$r = benchmark(fn() => $s->eagerReport());
echo "ms_tamsayı: " . (is_int($r["ms"]) ? "1" : "0") . "\n";
echo "bayt_tamsayı: " . (is_int($r["bytes"]) ? "1" : "0") . "\n";
Kendini test et
-
memory_get_usage çağrısını tamamla
$bytes = ____(true);Cevabı göster
$bytes = memory_get_usage(true); -
Performans kavramlarını eşleştir
- OPcache
- Bytecode'u bellekte tutar
- benchmark
- Süre ve bellek ölçer
- eager loading
- N+1 sorguyu önler
- memory_get_usage
- Bellek kullanımını verir
-
N+1 sorgu sorunu nedir?
- Tek sorguda N satırın çekilmesi
- Ana sorgu + her satır için ayrı sorgu yapılması
- JOIN ile birden fazla tabloyu birleştirme
- Önbelleğin devre dışı kalması
Cevabı göster
Ana sorgu + her satır için ayrı sorgu yapılması — Doğru. N+1, bir ana sorgu ve her satır için ayrı sorgu yapılmasıdır; eager loading ile önlenir.