---
title: 'Profil Çıkarma'
source: 'https://academia.sh/tr/kurslar/php-uretim/profil-cikarma'
course: "PHP'de Kalite, Başarım ve Dağıtım"
language: tr
updated: '2026-10-21T05:03:36+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Profil çıkarmanın (profiling) hangi soruya cevap verdiğini ve sıcak yolu (hot path) nasıl bulduğunu açıklayabilmek'
    - 'Aynı işin iki profilde aynı çağrı sayısını ve aynı bellek farkını verdiğini ölçerek göstermek'
    - 'Sayaç kodunu assert() içine koymanın profile göre sonucu neden değiştirdiğini yorumlamak'
---

# Profil Çıkarma

Bir işin hangi fonksiyonu kaç kez çağırdığını ve ne kadar bellek tuttuğunu iki profilde ölçmek: sayım aynı kalır, ama sayma kodu profile göre değişebilir.

Bir uygulama yavaş ya da şişkin göründüğünde ilk tepki tahmin etmektir. Profil çıkarma (profiling) tahmini ölçümle değiştirir: işin hangi fonksiyonu kaç kez çağırdığını ve çalışırken ne kadar bellek tuttuğunu kaydeder. En çok çağrılan ya da en çok bellek tutan yol, sıcak yol (hot path) diye anılır ve iyileştirmeye oradan başlanır. Bu derste süre ölçmüyoruz. Sayılar çağrı sayısı ve bellek farkıdır, çünkü bunlar makineden makineye değişmez; ölçülen şey, işin davranışıdır.

Ölçü ekseni bu kursun ortak sorusudur: bu davranış üretime çıkıyor mu? `yan.php` birebir aşağıdadır, bu derste de yalnız diske yazılır.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

Ölçüme başlamadan önce neyi ölçeceğimizi sınırlayalım. Bu derste iki büyüklük var: bir fonksiyonun çağrı sayısı ve bir işin çalışırken bellekte bıraktığı fark. İkisi de sayıdır ve ikisi de aynı kodu aynı girdiyle koşturduğunuzda aynı çıkar. Süre bu özelliğe sahip değildir: aynı iş bir makinede başka, bir başkasında başka sürer, hatta aynı makinede iki koşum arasında bile değişir. Bu yüzden karşılaştırmayı sayılar üzerine kurmak, ölçümü tekrar edilebilir kılar. Aşağıdaki iki blok bu mantıkla yazıldı; ilki ölçülen işi ve sayacı tanımlar, ikincisi o işi iki profilde koşturup sonuçları karşılaştırır.

Önce ölçeceğimiz işi ve sayaç sınıfını yazalım. `ikiKat()` fonksiyonu 500 kez çağrılır ve her çağrıda kendini sayar; ardından 10000 elemanlı bir dizi kurulur ve bellek farkı alınır. Dosyanın çıktısı yoktur, yoklamalar onu yükler.

```php
<?php
// sayac.php — çağrı sayısı ve bellek farkı ölçen küçük profil çıkarıcı
declare(strict_types=1);

final class Sayac {
    private static array $sayilar = [];

    public static function say(string $ad): void {
        self::$sayilar[$ad] = (self::$sayilar[$ad] ?? 0) + 1;
    }

    public static function oku(string $ad): int {
        return self::$sayilar[$ad] ?? 0;
    }
}

function ikiKat(int $n): int {
    Sayac::say('ikiKat');
    return $n * 2;
}

/** @return array{cagri: int, toplam: int, bellekFarki: int} */
function isiYap(): array {
    $once = memory_get_usage();
    $toplam = 0;
    for ($i = 0; $i < 500; $i++) {
        $toplam += ikiKat($i);
    }
    $liste = range(1, 10000);
    $fark = memory_get_usage() - $once;
    return ['cagri' => Sayac::oku('ikiKat'), 'toplam' => $toplam, 'bellekFarki' => $fark];
}
```

Şimdi iki profilde aynı işi koşturan yoklamayı yazalım. Yoklama profili `zend.assertions` değerinden tanır (geliştirmede `1`, üretimde `-1`) ve sonucu profile özgü bir dosyaya yazar. Sürücü iki dosyayı okuyup karşılaştırır. Bellek için sorulan, farkın iki profilde eşit olup olmadığıdır.

```php
<?php
// profil.php — aynı iş iki profilde aynı sayıyı verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('olcum.php', <<<'PHP'
<?php
// olcum.php — işi koşturur, sonucu profile özgü dosyaya yazar
require __DIR__ . '/sayac.php';
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
$sonuc = isiYap();
file_put_contents("olcum-$profil.json", json_encode($sonuc));
echo $sonuc['cagri'] === 500 && $sonuc['toplam'] === 249500 ? "evet\n" : "hayır\n";
PHP);

$yan = yanRaporu('500 çağrılık iş aynı sayıyı verir', 'olcum.php');

$gelistirme = json_decode(file_get_contents('olcum-gelistirme.json'), true);
$uretim = json_decode(file_get_contents('olcum-uretim.json'), true);
echo 'çağrı sayısı: ', $gelistirme['cagri'], ' / ', $uretim['cagri'], "\n";
echo 'toplam: ', $gelistirme['toplam'], ' / ', $uretim['toplam'], "\n";
echo 'bellek farkları eşit: ', $gelistirme['bellekFarki'] === $uretim['bellekFarki'] ? 'evet' : 'hayır', "\n";
```

```
500 çağrılık iş aynı sayıyı verir → iki yanda da
    geliştirme evet · üretim evet
çağrı sayısı: 500 / 500
toplam: 249500 / 249500
bellek farkları eşit: evet
```

İki profilde de çağrı sayısı 500 ve toplam 249500 çıktı, bellek farkları eşit kaldı. Profilin cevap verdiği soru "kaç kez ve ne kadar bellek" sorusudur ve bu soru iki yanda aynı cevabı alır. Dolayısıyla geliştirmede bulduğunuz sıcak yol, üretimde de sıcak yoldur. (**BA2.** Karşılaştırma tek süreçte ve tek bir koşumda yapıldı; bellek farkı, işin yaşadığı sürecin ayarlarına değil işin kendisine bağlıdır, bu derste ayrıca değiştirilmedi.)

## Sıcak Yolu Bulmak

Şimdiye kadar tek bir fonksiyonu saydık. Gerçek bir iş ise fonksiyonların birbirini çağırdığı bir zincirdir ve sıcak yol, zincirin en çok çağrılan halkasıdır. Aşağıdaki yoklama üç fonksiyonlu bir zincir kurar: `ozetle()` bir döngüde `donustur()` çağırır, `donustur()` ise her çağrıda `oku()` fonksiyonunu iki kez çağırır. `ozetle(40)` beş kez koşar. Çağrı sayıları baştan hesaplanabilir: `ozetle()` 5, `donustur()` 40 çarpı 5 yani 200, `oku()` ise 200 çağrının her birinde ikişer kez olduğundan 400 olmalıdır. Betik her fonksiyonun girişinde bir sayaç artırır, sayıları büyükten küçüğe sıralar ve ilk sıradaki adın `oku` olup olmadığını söyler. Dağılım her profilde ayrı bir dosyaya yazılır; sürücü iki dosyayı karşılaştırır ve sıralı dağılımı basar. Sayıların elle hesaplanabilir olması önemlidir: ölçüm beklentimizle örtüşüyorsa sayacın doğru çalıştığını da anlamış oluruz.

```php
<?php
// sicakyol.php — çağrı dağılımı iki profilde aynı mı, sıcak yol hangisi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('sicak.php', <<<'PHP'
<?php
// sicak.php — çağrı dağılımını çıkarır, en çok çağrılan fonksiyonu bulur
$sayilar = [];
function say(string $ad): void {
    global $sayilar;
    $sayilar[$ad] = ($sayilar[$ad] ?? 0) + 1;
}
function oku(int $i): int {
    say('oku');
    return $i % 7;
}
function donustur(int $i): int {
    say('donustur');
    return oku($i) * 3 + oku($i + 1);
}
function ozetle(int $n): int {
    say('ozetle');
    $toplam = 0;
    for ($i = 0; $i < $n; $i++) {
        $toplam += donustur($i);
    }
    return $toplam;
}
for ($k = 0; $k < 5; $k++) {
    ozetle(40);
}
arsort($sayilar);
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
file_put_contents("sicak-$profil.json", json_encode($sayilar));
echo array_key_first($sayilar) === 'oku' ? "evet\n" : "hayır\n";
PHP);

yanRaporu('sıcak yol oku fonksiyonudur', 'sicak.php');

$gelistirme = file_get_contents('sicak-gelistirme.json');
$uretim = file_get_contents('sicak-uretim.json');
foreach (json_decode($uretim, true) as $ad => $sayi) {
    echo $ad, ': ', $sayi, "\n";
}
echo 'dağılımlar eşit: ', $gelistirme === $uretim ? 'evet' : 'hayır', "\n";
```

```
sıcak yol oku fonksiyonudur → iki yanda da
    geliştirme evet · üretim evet
oku: 400
donustur: 200
ozetle: 5
dağılımlar eşit: evet
```

Çıktıyı satır satır okuyalım. İlk iki satır yoklamanın standart sonucudur: `sıcak yol oku fonksiyonudur` iddiası iki profilde de `evet` verdi, yani sıralamanın başında beklediğimiz fonksiyon var; sınıf `iki yanda da`dır. Sonraki üç satır sıralı dağılımdır: `oku` 400, `donustur` 200, `ozetle` 5. Bu sayılar elle yaptığımız hesapla birebir örtüşür. Örtüşme, ölçümün güvenilir olduğunu söyleyen asıl işarettir: sayaç beklenenden farklı bir şey verseydi önce sayacı, sonra kodu sorgulardık. Son satır, iki profilin dağılım dosyalarının baytı baytına aynı olduğunu söyler. Dosyaları sayı sayı değil bütün olarak karşılaştırmak, hem sayıların hem de sıralamanın aynı olduğunu tek adımda doğrular.

Bu dağılımın nasıl yorumlanacağı profil çıkarmanın asıl işidir. `ozetle()` en dıştaki fonksiyondur ve yalnız beş kez çağrılır; kodu okuyan biri onu önemli sanıp optimize etmeye yönelebilir. Dağılım ise işin yükünün alttaki `oku()` fonksiyonunda toplandığını gösterir: dört yüz çağrı, dıştaki fonksiyonun seksen katı. Optimizasyon bu fonksiyonun içinde ya da çağrı biçiminde aranır, örneğin sonucu önbelleğe almak, çağrıyı döngüden çıkarmak ya da iki çağrıyı tek çağrıya indirmek gibi. Bu derste süre ölçmediğimiz için "dört yüz çağrının ne kadar sürdüğünü" söylemiyoruz; çağrı sayısı incelemeye hangi fonksiyondan başlanacağını, yani sırayı belirler; iyileştirme kararını ise süre ile birlikte okunduğunda verir.

Yanın neden `iki yanda da` olduğunu da söyleyelim. Sayaç sıradan bir fonksiyon çağrısıdır; `zend.assertions` ya da `error_reporting` gibi profil farklarına bağlı bir yol kullanmaz. Aynı zincir iki profilde aynı sırayla koşar ve aynı sayıları üretir. Gerçek üretimde bu şu işe yarar: sıcak yolu geliştirme makinenizde bulup üretimde aynı yolun sıcak olduğuna güvenebilirsiniz, ama yalnızca sayma kodunuz profil farkına bağlı olmadığı sürece. Bir sonraki bölüm bu şartın neden önemli olduğunu gösteriyor.


## Sınırlayıcı Ölçüm

Profil çıkaran kodun kendisi de koddur ve iki profilde aynı davranmayabilir. `zend.assertions` değeri `-1` iken `assert()` çağrısının içindeki ifade hiç değerlendirilmez. Sayacı bir `assert()` içinde artırırsak iki profil aynı çağrı sayısını verir mi?

```php
<?php
// sinir.php — assert() içindeki sayaç iki profilde aynı sayıyı verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('assertli.php', <<<'PHP'
<?php
// assertli.php — sayacı assert() içinde artırır
$sayac = 0;
function say(): bool {
    global $sayac;
    $sayac++;
    return true;
}
for ($i = 0; $i < 500; $i++) {
    assert(say());
}
$profil = ini_get('zend.assertions') === '1' ? 'gelistirme' : 'uretim';
file_put_contents("assertli-$profil.txt", (string) $sayac);
echo $sayac === 500 ? "evet\n" : "hayır\n";
PHP);

yanRaporu('assert() içindeki sayaç artar', 'assertli.php');
echo 'geliştirme: ', file_get_contents('assertli-gelistirme.txt'), "\n";
echo 'üretim: ', file_get_contents('assertli-uretim.txt'), "\n";
```

```
assert() içindeki sayaç artar → yalnız geliştirmede
    geliştirme evet · üretim hayır
geliştirme: 500
üretim: 0
```

Geliştirmede sayaç 500, üretimde 0 çıktı; yan **yalnız geliştirmede** olarak okundu. Üretim profilinde `assert()` çağrısı derlenmediği için içindeki `say()` hiç koşmadı. Bunun anlamı açıktır: ölçümü tutan kod, durumu değiştiren bir çağrıysa onu `assert()` içine koymayın. Aynı işi iki profilde ölçerken sayaç ya her iki profilde koşan bir ifadede ya da `assert()` dışında tutulmalıdır. Profil çıkarırken hangi profilde çıkardığınız önemlidir; üretim profilinde ölçüp geliştirme varsayımlarıyla yorumlamak bu yüzden yanlış sayıya götürebilir.

İki ölçümü yan yana okuyunca iş bölümü görülür. Sayaç sınıfı sıradan bir fonksiyon çağrısıyla sayar ve iki profilde de aynı sonucu verir; sayma `assert()` içine itilince davranış profile bağlanır. Profil çıkarıcı araçlar da aynı kuralla çalışır: bir kaydı tutan kod, ölçtüğü kodun bulunduğu ayarla koşar. Sıcak yolu bulduktan sonra iyileştirmeyi yine aynı ölçümle doğrulamak, yani işi değiştirmeden önce ve sonra aynı sayıyı karşılaştırmak, sağlam yöntemdir.

Bu ölçüm bir uyarıyı da taşır. Sıcak yolu bulan kişi bulduğu yolun iyileştirilmesi gerektiğini düşünür; oysa çağrı sayısı yüksek olan fonksiyon ucuz da olabilir. Dört yüz çağrılık bir fonksiyon her çağrıda tek bir işlem yapıyorsa, beş çağrılık bir fonksiyondan çok daha az sorun yaratabilir. Profil çıkarmanın sağlam kullanımı, çağrı sayısını bellek farkıyla ve (bu dersin dışında kalan) süre ölçümüyle birlikte okumaktır. Bu dersin iki ölçümü, yani çağrı dağılımı ile bellek farkı, tek başına bir karar vermek için yeterli değil, iyileştirme sırasını belirlemek için yeterlidir.

Gerçek bir projede bu sayma işini elle yapmazsınız; bir profil çıkarıcı eklenti çağrıları kaydeder. Ancak bu eklentilerin kendisi de sürece yüklenir ve bazıları üretim profilinde kapalıdır ya da yüklenmeden koşar. Elle sayma bu yüzden bir öğretim aracıdır: bir sayma kodunun iki profilde aynı sonucu verdiğinin kanıtı, ölçümü yapan aracın aynı sonucu verdiğinin kanıtı değildir. Aracınızı üretim yanında çalıştırmadan önce, bu dersteki gibi küçük bir iş üzerinde iki yanda da aynı sayıları verdiğini doğrulayın.

Sınırlayıcı ölçümün ardında bir ayrım var: sayma kodunun kendisini ölçülen işin parçası saymak. `assert()` bu ayrımın bozulduğu yerdir, çünkü asıl amacı geliştirme sırasında varsayımları denetlemektir ve üretimde yok sayılır. Sayacı oraya koyan kişi geliştirmede beklenen sayıyı görür, üretimde sıfır görür ve ikisini aynı kod sanır. Çözüm basittir: sayma, `assert()` dışında bir ifadede yapılır; `assert()` yalnız doğruluk denetimi için kullanılır. Bunu kodda görmek zordur, çünkü iki profilde de hata çıkmaz; yalnız sayı sessizce farklı olur. Bu tür sessiz farkları yakalamanın tek yolu, ölçümü iki yanda koşturup sonuçları karşılaştırmaktır.

## Özet

- Profil çıkarma, "hangi fonksiyon kaç kez çağrıldı ve ne kadar bellek tutuldu" sorusunu ölçümle yanıtlar; en çok çağrılan yol sıcak yoldur.
- Bu dersin ölçü sınıfı **iki yanda da**dır: 500 çağrılık iş geliştirme ve üretim profillerinde aynı çağrı sayısını (500), aynı toplamı (249500) ve aynı bellek farkını verdi.
- Süre ve bayt sayısı yerine çağrı sayısı ile eşitlik karşılaştırması kullanıldı; bunlar makineden bağımsızdır.
- Sayaç `assert()` içinde artırılırsa sonuç profile bağlanır: geliştirmede 500, üretimde 0 çıkar, çünkü üretimde `assert()` içindeki ifade değerlendirilmez.
- Ölçümü tutan kod durumu değiştiren bir çağrıysa `assert()` içine konmaz.
- Üç fonksiyonlu zincirde çağrı dağılımı `oku` 400, `donustur` 200, `ozetle` 5 çıktı ve iki profilde baytı baytına aynı kaldı; sıcak yol en dıştaki değil en alttaki fonksiyondu (ölçü sınıfı: iki yanda da).

## Sonraki Adım

Çağrı sayısı ve bellek farkı, işin ne yaptığını gösterir; ama bellek sınırı aşıldığında ne olduğunu göstermez. Sonraki ders `memory_limit` aşılınca isteğin nasıl durduğunu, bağlantı nesnelerinin istek sonunda nasıl kapandığını ve döngüsel bir referansın yıkıcıyı ne zaman geciktirdiğini ölçer.
