Bir değişkenin bellek ayak izi (boyutu) nasıl belirlenir?


103

PHP'de (veya bir PHP uzantısı) belirli bir değişkenin ne kadar bellek kullandığını bulmak için bir işlev var mı? sizeofbana sadece elemanların / özelliklerin sayısını söyler.

memory_get_usagebana tüm komut dosyası tarafından kullanılan bellek boyutunu vermesi konusunda yardımcı oluyor . Bunu tek bir değişken için yapmanın bir yolu var mı?

Bunun bir geliştirme makinesinde olduğunu unutmayın, bu nedenle uzantıları veya hata ayıklama araçlarını yüklemek mümkündür.


Düzenlendi - 5 yıl sonra ve bazı sorunlar hala bir şekilde çözülmedi :(
Piskvor

Yanıtlar:


48

Muhtemelen bir Bellek Profilleyicisine ihtiyacınız vardır. SO'dan bilgi topladım ama size de yardımcı olabilecek bazı önemli şeyleri kopyaladım.

Muhtemelen bildiğiniz gibi, Xdebug 2. * sürümünden beri bellek profili oluşturma desteğini bıraktı. Lütfen burada "kaldırılan işlevler" dizesini arayın: http://www.xdebug.org/updates.php

Kaldırılan işlevler

Düzgün çalışmadığı için Bellek profili oluşturma desteği kaldırıldı.

Diğer Profiler Seçenekleri

php-memory-profiler

https://github.com/arnaud-lb/php-memory-profiler . Bunu etkinleştirmek için Ubuntu sunucumda yaptığım şey bu:

sudo apt-get install libjudy-dev libjudydebian1
sudo pecl install memprof
echo "extension=memprof.so" > /etc/php5/mods-available/memprof.ini
sudo php5enmod memprof
service apache2 restart

Ve sonra kodumda:

<?php
memprof_enable();
// do your stuff
memprof_dump_callgrind(fopen("/tmp/callgrind.out", "w"));

Sonunda callgrind.outdosyayı KCachegrind ile açın

Google gperftools'u kullanma (önerilir!)

Öncelikle en son paketi buradan indirerek Google gperftools'u yükleyin : https://code.google.com/p/gperftools/

Sonra her zamanki gibi:

sudo apt-get update
sudo apt-get install libunwind-dev -y
./configure
make
make install

Şimdi kodunuzda:

memprof_enable();

// do your magic

memprof_dump_pprof(fopen("/tmp/profile.heap", "w"));

Ardından terminalinizi açın ve başlatın:

pprof --web /tmp/profile.heap

pprof , mevcut tarayıcı oturumunuzda aşağıda gösterildiği gibi yeni bir pencere oluşturacaktır:

Memprof ve gperftools ile PHP bellek profili oluşturma

Xhprof + Xhgui (bence hem cpu hem de bellek profili için en iyisi)

İle Xhprof ve Xhgui o Sorununuz şu anda ise siz de ya da sadece bellek kullanımı CPU kullanımını profil olabilir. Çok eksiksiz bir çözümdür, size tam kontrol sağlar ve günlükler hem mongo'da hem de dosya sisteminde yazılabilir.

Daha fazla ayrıntı için buraya bakın .

Siyah ateş

Blackfire, SensioLabs, the Symfony2'nin adamları https://blackfire.io/ tarafından hazırlanan bir PHP profilleyicisidir.

Sanal makinenizi kurmak için kukla kullanıyorsanız , desteklendiğini bilmekten mutluluk duyacaksınız ;-)

Xdebug ve bellek kullanımını izleme

XDEBUG2 , PHP için bir uzantıdır. Xdebug, parametreler dahil tüm işlev çağrılarını günlüğe kaydetmenize ve değerleri bir dosyaya farklı biçimlerde döndürmenize olanak tanır. Üç çıktı biçimi vardır. Biri, insan tarafından okunabilir bir izleme anlamına gelir, diğeri, ayrıştırılması daha kolay olduğu için bilgisayar programları için daha uygundur ve sonuncusu, izlemeyi biçimlendirmek için HTML kullanır. Ayarla iki farklı format arasında geçiş yapabilirsiniz. Burada bir örnek mevcut olacak

P için

forp basit, müdahaleci olmayan, üretim odaklı, PHP profil oluşturucusu. Bazı özellikler şunlardır:

  • her işlev için zaman ve ayrılmış bellek ölçümü

  • CPU kullanımı

  • fonksiyon çağrısının dosya ve satır numarası

  • Google'ın İzleme Olayı biçimi olarak çıktı

  • fonksiyonların başlığı

  • fonksiyonların gruplanması

  • işlevlerin takma adları (anonim işlevler için kullanışlıdır)

DBG

DBG , php betiklerinde hata ayıklamanıza yardımcı olan etkileşimli bir araç olan tam özellikli bir php hata ayıklayıcıdır. Bir üretim ve / veya geliştirme WEB sunucusunda çalışır ve komut dosyalarınızda yerel veya uzaktan, bir IDE veya konsoldan hata ayıklamanıza olanak tanır ve özellikleri şunlardır:

  • Uzak ve yerel hata ayıklama

  • Açık ve örtük aktivasyon

  • Parametreleri ile işlev çağrıları, dinamik ve statik yöntem çağrıları dahil çağrı yığını

  • Karşılık gelen (iç içe) yerlerde değişkenleri değerlendirme yeteneği ile çağrı yığını arasında gezinme

  • İçeri adımla / Dışarı adımla / Adımla / İmleç işlevselliğine koş

  • Koşullu kesme noktaları

  • Global kesme noktaları

  • Hatalar ve uyarılar için günlük kaydı

  • Paralel hata ayıklama için birden çok eşzamanlı oturum

  • GUI ve CLI ön uçları için destek

  • IPv6 ve IPv4 ağları desteklenir

  • Hata ayıklayıcı tarafından aktarılan tüm veriler isteğe bağlı olarak SSL ile korunabilir


2
Tam da aradığım bilgi buydu , teşekkürler.
Piskvor,

93

Tek bir değişkenin bellek kullanımını elde etmenin doğrudan bir yolu yoktur, ancak Gordon'un önerdiği gibi kullanabilirsiniz memory_get_usage. Bu, ayrılan toplam bellek miktarını döndürür, böylece bir geçici çözüm kullanabilir ve tek bir değişkenin kullanımını elde etmek için öncesi ve sonrası kullanımı ölçebilirsiniz. Bu biraz karmaşık ama işe yaramalı.

$start_memory = memory_get_usage();
$foo = "Some variable";
echo memory_get_usage() - $start_memory;

Bunun hiçbir şekilde güvenilir bir yöntem olmadığını unutmayın, değişkeni atarken başka hiçbir şeyin belleğe dokunmadığından emin olamazsınız, bu nedenle bu yalnızca bir yaklaşım olarak kullanılmalıdır.

İşlev içinde değişkenin bir kopyasını oluşturarak ve kullanılan belleği ölçerek bunu bir işleve dönüştürebilirsiniz. Bunu test etmedim, ancak prensip olarak bunda yanlış bir şey görmüyorum:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $tmp = unserialize(serialize($var));
    return memory_get_usage() - $start_memory;
}

14
$tmp = $varsığ bir kopya oluşturacaktır. Bu, $ tmp değiştirilene kadar daha fazla bellek ayırmayacaktır.
Gordon

@Gordon, haklısın, bu noktayı gözden kaçırdım. Değişkeni türünü veya boyutunu değiştirmeden değiştirmenin uygun bir yolunu bulamadığım için, bunu öyle bırakacağım. Belki birisi uygun bir fikir bulabilir :)
Tatu Ulmanen

7
ne dersin $tmp = unserialize(serialize($var)); Bu, Aistina'nın yukarıdaki yaklaşımını birleştirecektir.
Gordon

3
ayrıca, $varişleve geçirilen şeyin zaten sığ bir kopyası veya referansı olduğundan, ihtiyacınız yoktur $tmp, ancak yeniden atayabilirsiniz $var. Bu iç referansı kaydeder $tmpiçin $var.
Gordon

KQUEUE biraz daha zarif bir yolu yok $tmpdan $var?
Tomáš Zato -

24

Hayır yok. Ancak bir tahmin için sonucu serialize($var)kontrol edebilir ve kontrol edebilirsiniz strlen.


Tüm GC olayını ortadan kaldırdığı için bu çok daha iyi bir yaklaşımdır.
Gleno

12
Bu korkunç bir yaklaşım. PHP'de bir dizideki her öğe ~ 80 bayttır, ancak yine strlen(serialize(array(1,2,3)))de 30'dur.
gsnedders

2
@Aistina, -1. yanlış şeyi ölçüyorsun. Değişken ve serileştirilmiş değişken tamamen farklı iki şeydir ve tamamen farklı sonuçlar verecektir.
Pacerier

1
Sadece bu da değil , belirli serileştirilemez veri yapılarında, örneğin dairesel referanslarda tamamen başarısız olur .
duskwuff -inaktif

20

Tatu Ulmanens cevabına göre:

$start_memoryHafızayı ( PHP_INT_SIZE * 8) alacağına dikkat edilmelidir .

Böylece tüm işlev şu hale gelmelidir:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $var = unserialize(serialize($var));
    return memory_get_usage() - $start_memory - PHP_INT_SIZE * 8;
}

Bunu fazladan bir cevap olarak eklediğim için üzgünüm ama henüz bir cevaba yorum yapamıyorum.

Güncelleme: * 8 kesin değildir. Görünüşe göre php sürümüne ve muhtemelen 64/32 bitine bağlı olabilir.


4
Nedenini açıklayabilir misin * 8? Teşekkürler!
sierrasdetandil

@sierrasdetandil Görünüşe göre $ start_memory sadece PHP_INT_SIZEbayt almıyor , değil PHP_INT_SIZE*8. Bunu, bu işlevi çağırarak deneyebilirsiniz, 0:function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }
para

8sabit görünmüyor. Dev sistemimde (PHP 5.6.19) yorum işlevinizin ardından geri dönüyor -16. Ayrıca, ilginç bir şekilde, php -aişlevin iki satırını çağırmaktan çeşitli farklı değerler verir.
Paul DelRe

@PaulDelRe evet, muhtemelen bu tür şeyler sürüm / 64bit'e bağlıdır.
para

artık ölümcül hata unserialize () çağrısında meydana geliyor. Yardımı yok! Bir değişken çok büyükse hafızası biter, bu değişken üzerinde bir fonksiyon çağırmak DAHA FAZLA hafıza kullanır. :(
john ktejik

4

Görmek:

Bunun size belirli bir değişkenin bellek kullanımını vermeyeceğini unutmayın. Ancak, değişken atamadan önce ve sonra bu işlevi çağırabilir ve ardından değerleri karşılaştırabilirsiniz. Bu size kullanılan hafıza hakkında bir fikir vermelidir.

PECL uzantısı Memtrack'e de bir göz atabilirsiniz , ancak dokümantasyon biraz eksik olsa da, neredeyse hiç yok.


Evet. Dolaylı olarak soruyu cevaplamak için kullanılabilir.
Notinlist

3

Geri arama dönüş değerinde bellek farkını hesaplamayı tercih edebilirsiniz. PHP 5.3+ sürümünde bulunan daha zarif bir çözümdür.

function calculateFootprint($callback) {
    $startMemory = memory_get_usage();
    $result = call_user_func($callback);
    return memory_get_usage() - $startMemory;
}

$memoryFootprint = calculateFootprint(
    function() {
        return range(1, 1000000);
    }
);

echo ($memoryFootprint / (1024 * 1024)) . ' MB' . PHP_EOL;

3

İki değişken bellekte aynı ayrılmış alanı paylaşabileceğinden, bir değişkenin tam ayak izini geriye dönük olarak hesaplayamazsınız

Hafızayı iki dizi arasında paylaşmaya çalışalım, ikinci diziyi ayırmanın ilkinin belleğinin yarısına mal olduğunu görüyoruz. Birincisinin ayarını kaldırdığımızda, neredeyse tüm hafıza hala ikincisi tarafından kullanılır.

echo memory_get_usage()."\n"; // <-- 433200
$c=range(1,100);
echo memory_get_usage()."\n"; // <-- 444348 (+11148)
$d=array_slice($c, 1);
echo memory_get_usage()."\n"; // <-- 451040 (+6692)
unset($c);
echo memory_get_usage()."\n"; // <-- 444232 (-6808)
unset($d);
echo memory_get_usage()."\n"; // <-- 433200 (-11032)

Yani ikinci dizinin hafızanın yarısını kullandığı sonucuna varamayız, çünkü ilk diziyi kaldırdığımızda yanlış olur.

PHP'de belleğin nasıl tahsis edildiğini ve hangi amaçla kullanıldığını tam olarak görmek için aşağıdaki makaleyi okumanızı öneririm: PHP dizileri (ve değerleri) gerçekte ne kadar büyük? (İpucu: BÜYÜK!)

Referans Sayma Temelleri PHP belgelerinde ayrıca hafıza kullanımı hakkında birçok bilgi vardır ve referanslar paylaşılan veri segmentine sayar.

Burada açığa çıkan farklı çözümler tahminler için iyidir ancak hiçbiri PHP belleğinin ince yönetimini kaldıramaz.

  1. yeni tahsis edilen alanın hesaplanması

Bir atamadan sonra yeni tahsis edilen alanı istiyorsanız memory_get_usage(), tahsisattan önce ve sonra kullanmanız gerekir, çünkü onu bir kopya ile kullanmak size gerçeğin yanlış bir görünümünü verir.

// open output buffer
echo "Result: ";
// call every function once
range(1,1); memory_get_usage();

echo memory_get_usage()."\n";
$c=range(1,100);
echo memory_get_usage()."\n";

Birincinin sonucunu saklamak istiyorsanız memory_get_usage(), değişkenin daha önce var memory_get_usage()olması ve bir önceki sefer ve diğer her işlevin de çağrılması gerektiğini unutmayın.

Yukarıdaki örnekte olduğu gibi yankı yapmak istiyorsanız, çıktı arabelleğini açmak için gereken hesaplama belleğini önlemek için çıktı arabelleğiniz zaten açılmış olmalıdır.

  1. gerekli alanı hesaplamak

Bir değişkenin bir kopyasını depolamak için gerekli alanı hesaplamak için bir işleve güvenmek istiyorsanız, aşağıdaki kod farklı optimizasyonları ele alır:

<?php
function getMemorySize($value) {
    // existing variable with integer value so that the next line
    // does not add memory consumption when initiating $start variable
    $start=1;
    $start=memory_get_usage();
    // json functions return less bytes consumptions than serialize
    $tmp=json_decode(json_encode($value));
    return memory_get_usage() - $start;
}

// open the output buffer, and calls the function one first time
echo ".\n";
getMemorySize(NULL);

// test inside a function in order to not care about memory used
// by the addition of the variable name to the $_GLOBAL array
function test() {
    // call the function name once 
    range(1,1);

    // we will compare the two values (see comment above about initialization of $start)
    $start=1;
    $start=memory_get_usage();
    $c=range(1,100);
    echo memory_get_usage()-$start."\n";
    echo getMemorySize($c)."\n";
}
test();

// same result, this works fine.
// 11044
// 11044

Ayrılan bellekteki değişken adının boyutunun önemli olduğuna dikkat edin.

  1. Kodunuzu kontrol edin !!

Bir değişken, PHP kaynak kodunda kullanılan iç C yapısı tarafından tanımlanan temel bir boyuta sahiptir. Sayılar söz konusu olduğunda bu boyut dalgalanmaz. Dizeler için dizenin uzunluğunu ekler.

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

Değişken adının ilklendirilmesini hesaba katmazsak, bir değişkenin ne kadar kullandığını zaten biliyoruz (sayılar ve dizeler durumunda):

Sayılar durumunda 44 bayt

Dizeler durumunda + 24 bayt

+ dizenin uzunluğu (son NUL karakteri dahil)

(bu sayılar PHP sürümüne göre değişebilir)

Bellek hizalaması nedeniyle 4 bayt katına yuvarlamanız gerekir. Değişken global alandaysa (bir işlevin içinde değilse), 64 bayt daha tahsis edecektir.

Dolayısıyla, bu sayfadaki kodlardan birini kullanmak istiyorsanız, bazı basit test senaryoları (dizeler veya sayılar) kullanan sonucun, bu gönderideki göstergelerin her birini ($ _GLOBAL dizisi, ilk işlev çağrısı, çıktı tamponu, ...)


1
... ve dahili özelliklerine girmeden önce bile olduğunu zvalue, is_refve copy-on-write. Teşekkür ederim.
Piskvor

1
Sayende PHP Kılavuzundaki o sayfayı kaçırdım. Cevabımı tamamlamak için bağlantıyı ekledim (ama sanırım bunu zaten okumuştunuz).
Adam

2

Benzer bir problemim vardı ve kullandığım çözüm, değişkeni bir dosyaya yazmak ve ardından dosya üzerinde dosya boyutu () çalıştırmaktı. Kabaca şöyle (test edilmemiş kod):

function getVariableSize ( $foo ) 
{
    $tmpfile = "temp-" . microtime(true) . ".txt";
    file_put_contents($tmpfile, $foo);
    $size = filesize($tmpfile);
    unlink($tmpfile);
    return $size;
}

Bu çözüm çok hızlı değil çünkü disk IO'yu içeriyor, ancak size memory_get_usage hilelerinden çok daha kesin bir şey vermeli. Bu sadece ne kadar hassasiyete ihtiyacınız olduğuna bağlıdır.


Bu çözümün yalnızca dizgi ve tek boyutlu dizge dizisi için çalıştığı ve strlenkullanımının daha kolay olacağı unutulmamalıdır.
Adam


1
function mesure($var){
    $start = memory_get_usage();
    if(is_string($var)){
        $newValue = $var . '';
    }elseif(is_numeric($var)){
        $newValue = $var + 0;
    }elseif(is_object($var)){
        $newValue = clone $var;
    }elseif(is_array($var)){
        $newValue = array_flip($var, []);
    }
    return memory_get_usage() - $start;
}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.