PHP 5.5 generator (yield) ile büyük veri kümelerini gezmek
Belleği şişirmeden büyük döngüler kurmanın dil düzeyindeki yolu: PHP 5.5 generator ve yield kullanımı.
Bu yıl Laravel, Composer ve Git gibi araçlarla geçti; son yazıda dil düzeyinde bir özelliğe odaklanmak istedim. PHP 5.5 ile gelen generator (üreteç) ve yield anahtar kelimesini keşfettim; büyük veri kümeleriyle çalışırken işe nasıl yaradığını paylaşmak istiyorum.
Problem: Büyük dizi bellekte şişmek
Diyelim ki bir CSV dosyasında 500.000 satır var ve bunları işleyeceğim. Saf yaklaşım her şeyi diziye almak:
<?php
function csvSatirlariniOku($dosya)
{
$satirlar = [];
$f = fopen($dosya, 'r');
while (($satir = fgetcsv($f)) !== false) {
$satirlar[] = $satir;
}
fclose($f);
return $satirlar;
}
$tumSatirlar = csvSatirlariniOku('buyuk.csv');
foreach ($tumSatirlar as $satir) {
// işle
}
Bu kod 500.000 satırın tamamını belleğe (memory) yüklüyor. Küçük dosyalarda sorun yok. Büyük dosyada memory_limit aşımıyla karşılaşabilirsiniz.
Bunu ilk gerçek proje üzerinde gördüm: bir müşterinin aktarım sisteminde günlük CSV dosyaları 300-400 bin satıra çıkıyordu. Script yarı yolda Fatal error: Allowed memory size exhausted ile duruyordu. memory_limit ini değerini artırmak çözüm gibi görünse de bu sadece sorunu erteliyordu; dosya boyutu artmaya devam edecekti.
Generator nedir?
Generator (Üreteç), değerleri hepsini bir arada hesaplamak yerine birer birer, talep üzerine üreten bir fonksiyon türüdür. Normal bir fonksiyondan farkı return yerine yield anahtar kelimesini kullanmasıdır. Bir değeri üretir, duraklar; sonraki çağrıda kaldığı yerden devam eder.
PHP 5.5 ile dile eklendi.
yield ile aynı örnek
<?php
function csvSatirlariniOkuGenerator($dosya)
{
$f = fopen($dosya, 'r');
while (($satir = fgetcsv($f)) !== false) {
yield $satir;
}
fclose($f);
}
foreach (csvSatirlariniOkuGenerator('buyuk.csv') as $satir) {
// Her iterasyonda bellekte yalnızca bir satır var
}
Görünüşte küçük bir fark; ama çalışma biçimi çok farklı. yield her çalıştığında o anki $satir değerini dışarıya veriyor ve fonksiyon duruyor. foreach bir sonraki değeri istediğinde fonksiyon kaldığı yerden devam ediyor. Bellekte 500.000 satır yerine hep tek bir satır var.
Sayı aralığı örneği
Generator’ın ne işe yaradığını görmek için sayı dizisi üretici basit bir örnek:
<?php
function aralik($baslangic, $bitis)
{
for ($i = $baslangic; $i <= $bitis; $i++) {
yield $i;
}
}
foreach (aralik(1, 1000000) as $sayi) {
echo $sayi . "\n";
}
PHP’nin yerleşik range(1, 1000000) fonksiyonu 1 milyonluk bir dizi oluşturur ve tamamını belleğe yükler. Generator versiyonu ise her adımda yalnızca o anki sayıyı tutar.
Generator nesnesi
Generator fonksiyonu çağrıldığında hemen çalışmaz; bir Generator nesnesi döner. Bu nesneyi foreach ile döngüye sokmak da mümkün, ya da manuel kontrol etmek de:
<?php
function ureticim()
{
yield 'birinci';
yield 'ikinci';
yield 'ucuncu';
}
$uretec = ureticim();
echo $uretec->current(); // birinci
$uretec->next();
echo $uretec->current(); // ikinci
current() mevcut değeri, next() bir sonraki adıma geçişi sağlıyor. Pratikte bu düzeyde manuel kontrol nadiren gerekiyor; foreach çoğu durumda yeterli.
yield ile anahtar-değer çiftleri
Normal dizi gibi anahtar-değer üretmek de mümkün:
<?php
function veriUret()
{
yield 'ad' => 'Ahmet';
yield 'email' => '[email protected]';
yield 'yas' => 30;
}
foreach (veriUret() as $anahtar => $deger) {
echo "{$anahtar}: {$deger}\n";
}
Dikkat: generator yeniden başlatılamaz
Bir generator’ı yalnızca bir kez gezeblirsiniz. foreach döngüsü tamamlandıktan sonra aynı generator nesnesini yeniden döngüye sokmaya kalkışırsanız boş geçer; değerler yeniden üretilmez. Eğer aynı veriyi iki kez işlemeniz gerekiyorsa fonksiyonu yeniden çağırmanız ve yeni bir generator nesnesi almanız gerekiyor. Bu, sıradan diziden temel farkı; unutunca sessizce hatalı davranış üretebilir.
Ne zaman kullanmalı?
Her yerde generator kullanmak şart değil. Veri kümesi sınırlı ve belleğe rahatça sığıyorsa normal dizi daha basit.
Generator işe yarıyor:
- Büyük dosyaları satır satır işlerken
- Veritabanından çok sayıda kayıt çekerken ve hepsine aynı anda ihtiyaç olmadığında
- Sonsuz veya çok büyük bir sırayla çalışırken (API sayfalaması, akış verisi)
Bu özelliği öğrenmek için somut bir ihtiyacım oldu: bir projede günlük CSV içe aktarma işlemi bellek sınırına takılıyordu. Generator ile çözdüm; şimdi ne zaman büyük veri döngüsü yazacağım aklımın bir köşesinde bu seçenek duruyor.
2014’ün son yazısı olarak biraz farklı bir konu seçmek istedim. Bir sonraki yıl Laravel’in yeni sürümüyle devam edeceğim.