Як визначити слід (розмір) пам'яті змінної?


102

Чи є в PHP функція (або розширення PHP), щоб дізнатися, скільки пам'яті використовує дана змінна? sizeofпросто підказує мені кількість елементів / властивостей.

memory_get_usageдопомагає в тому, що він дає мені розмір пам'яті, який використовується всім сценарієм. Чи є спосіб це зробити для однієї змінної?

Зауважте, що це на машині розробки, тому завантаження розширень чи інструментів налагодження можливо.


Відредаговано - це через 5 років, і деякі проблеми все ще дещо
вирішені

Відповіді:


46

Ймовірно, вам потрібен Profiler пам'яті. Я зібрав інформацію так, але я скопіював важливу річ, яка також може вам допомогти.

Як ви, напевно, знаєте, Xdebug відмовився від підтримки для профілювання пам'яті з версії 2. *. Будь ласка, знайдіть рядок "видалені функції" тут: http://www.xdebug.org/updates.php

Видалені функції

Видалена підтримка профілювання пам'яті, оскільки вона не працювала належним чином.

Інші параметри профіля

php-пам'ять-профілер

https://github.com/arnaud-lb/php-memory-profiler . Це те, що я зробив на своєму сервері Ubuntu, щоб увімкнути це:

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

А потім у моєму коді:

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

Нарешті відкрийте callgrind.outфайл за допомогою KCachegrind

Використання Google gperftools (рекомендується!)

Перш за все, встановіть Google gperftools , завантаживши тут останній пакет: https://code.google.com/p/gperftools/

Тоді як завжди:

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

Тепер у вашому коді:

memprof_enable();

// do your magic

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

Потім відкрийте свій термінал і запустіть:

pprof --web /tmp/profile.heap

pprof створить нове вікно в існуючому сеансі веб-переглядача з чимось таким, як показано нижче:

Профілювання пам'яті PHP за допомогою memprof та gperftools

Xhprof + Xhgui (найкращий, на мій погляд, профіль як процесора, так і пам'яті)

За допомогою Xhprof та Xhgui ви можете також профілювати використання процесора або просто використання пам'яті, якщо це зараз ваша проблема. Це дуже повноцінне рішення, воно дає вам повний контроль і журнали можна записувати як на mongo, так і у файлову систему.

Детальніше дивіться тут .

Чорна пожежа

Blackfire - це PHP-профайл від SensioLabs, хлопців Symfony2 https://blackfire.io/

Якщо ви використовуєте puphpet для налаштування своєї віртуальної машини, ви будете раді знати, що вона підтримується ;-)

Xdebug та відстеження використання пам'яті

XDEBUG2 - це розширення для PHP. Xdebug дозволяє реєструвати всі виклики функцій, включаючи параметри та повертаючі значення у файл у різних форматах. Є три вихідні формати. Один мається на увазі як читабельний людський слід, інший більше підходить для комп'ютерних програм, оскільки його простіше розбирати, а останній використовує HTML для форматування трасування. Ви можете перемикатися між двома різними форматами за допомогою налаштування. Приклад можна знайти тут

форп

forp простий, не нав'язливий, орієнтований на виробництво, PHP-профілер. Деякі особливості:

  • вимірювання часу та виділеної пам’яті для кожної функції

  • Використання процесора

  • номер і номер рядка виклику функції

  • вихід у форматі Google Trace Event

  • титр функцій

  • групування функцій

  • псевдоніми функцій (корисні для анонімних функцій)

DBG

DBG - це повнофункціональний налагоджувач php, інтерактивний інструмент, який допомагає налагоджувати сценарії PHP. Він працює на виробництві та / або розробці WEB-сервера і дозволяє налагоджувати свої сценарії локально або віддалено з IDE або консолі, і його функції:

  • Віддалена та локальна налагодження

  • Явна та неявна активація

  • Стек викликів, включаючи функціональні виклики, динамічні та статичні виклики методів із їх параметрами

  • Навігація через стек виклику з можливістю оцінювання змінних у відповідних (вкладених) місцях

  • Крок / Вихід / Перехід / Перехід до функціональності курсору

  • Умовні точки прориву

  • Глобальні точки прориву

  • Реєстрація помилок та попереджень

  • Кілька одночасних сеансів для паралельної налагодження

  • Підтримка інтерфейсів GUI та CLI

  • Підтримуються мережі IPv6 та IPv4

  • Всі дані, передані налагоджувачем, можуть бути додатково захищені SSL


2
Це саме та інформація, яку я шукав, дякую.
Пісквор вийшов з будівлі

93

Немає прямого способу отримати в пам'яті використання однієї змінної, але, як запропонував Гордон, ви можете використовувати memory_get_usage. Це поверне загальний обсяг виділеної пам’яті, тому ви можете використовувати обхідне рішення і вимірювати використання до і після, щоб отримати використання однієї змінної. Це трохи хакі, але це має працювати.

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

Зауважте, що це жодним чином не є надійним методом, ви не можете бути впевнені, що більше нічого не торкається пам'яті під час призначення змінної, тому це слід використовувати лише як наближення.

Ви можете фактично перетворити це на функцію, створивши копію змінної всередині функції та вимірявши використану пам'ять. Я цього не перевіряв, але в принципі я не бачу в цьому нічого поганого:

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

14
$tmp = $varстворить дрібну копію. Це не виділить більше пам’яті, поки $ tmp не буде змінено.
Гордон

@ Gordon, ти маєш рацію, я якось не помітив цього пункту. Оскільки я не можу знайти належний спосіб зміни змінної, не змінюючи її тип чи розмір, я залишу це. Можливо, хтось може придумати належну ідею :)
Tatu Ulmanen

7
як щодо $tmp = unserialize(serialize($var)); Це поєднало б підхід Айстіна вище.
Гордон

3
також, оскільки $varце вже неглибока копія або посилання на те, що було передано функції, вам не потрібно $tmp, але можна перепризначити їх $var. Це зберігає внутрішню посилання від $tmpдо $var.
Гордон

Чи немає більш елегантний спосіб разименованія $tmpвід $var?
Томаш Зато - Відновити Моніку

24

Ні, немає. Але ви можете serialize($var)і перевірити strlenрезультат для наближення.


Це набагато кращий підхід, оскільки він уникає всієї справи в GC.
Глено

12
Це жахливе наближення. Кожен елемент масиву в PHP становить ~ 80 байт, ще strlen(serialize(array(1,2,3)))- 30.
gsnedders

2
@Aistina, -1. ти вимірюєш неправильну річ. Змінна і серіалізована змінна - це дві абсолютно різні речі і дадуть абсолютно різні результати.
Pacerier

1
Не тільки це, але й повністю вийде з ладу на певних несеризаційних структурах даних, наприклад, кругових посиланнях.
duskwuff -inactive-

20

У відповідь Тату Ульманс відповідь:

Слід зазначити, що $start_memoryсама по собі займе пам'ять ( PHP_INT_SIZE * 8).

Отже вся функція повинна стати:

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

Вибачте, що додав це як додаткову відповідь, але поки не можу коментувати відповідь.

Оновлення: * 8 не визначається. Мабуть, це може залежати від версії php та, можливо, від 64/32 біт.


4
Чи можете ви пояснити, чому * 8? Дякую!
sierrasdetandil

@sierrasdetandil Здається, що $ start_memory не займає лише PHP_INT_SIZEбайти, але PHP_INT_SIZE*8. Ви можете спробувати це, зателефонувавши до цієї функції, вона повинна повернути 0:function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }
пункт

8не здається постійним. Після вашої функції коментарів у моїй програмі розробки (PHP 5.6.19) вона повертається -16. Крім того, що цікаво, php -aвиклик двох рядків функції дає різні різні значення.
Paul DelRe

@PaulDelRe так, ймовірно, це залежить від версії / 64bit подібних матеріалів.
пункт

тепер фатальна помилка трапляється під час виклику unserialize (). Це не допомога! Якщо змінна настільки велика, їй не вистачає пам'яті, виклик функції цієї змінної витратить навіть БОЛЬШУ пам'ять. :(
John ktejik

4

Побачити:

Зауважте, що це не дозволить вам використовувати пам'ять певної змінної. Але ви можете ставити дзвінки на цю функцію до і після призначення змінної, а потім порівнювати значення. Це має дати вам уявлення про використану пам'ять.

Ви також можете ознайомитись з розширенням PECL Memtrack , хоча документації трохи не вистачає, якщо не сказати, практично не існує.


Так. Його можна використовувати опосередковано для відповіді на питання.
Notinlist

3

Ви можете вибрати обчислення різниці пам'яті за значенням зворотного виклику. Це більш елегантне рішення, доступне в PHP 5.3+.

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

Ви не можете ретроспективно обчислити точний слід змінної, оскільки дві змінні можуть поділяти один і той же виділений простір у пам'яті

Спробуємо поділити пам'ять між двома масивами, бачимо, що виділення другого масиву коштує половину пам'яті першого. Коли ми скидаємо перший, майже вся пам'ять все ще використовується другою.

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)

Тому ми не можемо зробити висновок, що другий масив використовує половину пам'яті, оскільки він стає помилковим, коли ми скидаємо перший.

Для повного перегляду того, як розподіляється пам'ять у PHP та для якого використання, пропоную прочитати наступну статтю: Наскільки насправді масиви (та значення) PHP є великими? (Підказка: ВЕЛИКИЙ!)

В Основах підрахунку посилань в документації PHP також має багато інформації про використання пам'яті, а також підраховувати посилання на розділяється сегмент даних.

Тут розкриваються різні рішення, які корисні для наближення, але жодне не може впоратися з тонким керуванням пам'яттю PHP.

  1. розрахунок щойно виділеного простору

Якщо ви хочете, щоб після призначення було виділено простір після призначення, вам доведеться використовувати його memory_get_usage()до і після розподілу, оскільки використання його з копією дає помилковий погляд на реальність.

// 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";

Пам'ятайте, що якщо ви хочете зберегти результат першого memory_get_usage(), змінна повинна існувати і раніше, і memory_get_usage()повинна бути викликана іншим попереднім часом, і всі інші функції також.

Якщо ви хочете лунати, як у наведеному вище прикладі, ваш вихідний буфер повинен бути вже відкритим, щоб уникнути пам'яті обліку, необхідної для відкриття вихідного буфера.

  1. розрахунок необхідного місця

Якщо ви хочете покластися на функцію для обчислення необхідного простору для зберігання копії змінної, наступний код має на увазі різні оптимізації:

<?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

Зауважте, що розмір імені змінної має значення у виділеній пам'яті.

  1. Перевір свій код !!

Змінна має базовий розмір, визначений внутрішньою структурою С, що використовується у вихідному коді PHP. Цей розмір не коливається у випадку чисел. Для рядків це додало б довжину рядка.

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;

Якщо ми не беремо до уваги ініціалізацію імені змінної, ми вже знаємо, скільки використовує змінна (у випадку чисел та рядків):

44 байти у випадку чисел

+ 24 байти у разі рядків

+ довжина рядка (включаючи кінцевий символ NUL)

(ці цифри можуть змінюватися залежно від версії PHP)

Через вирівнювання пам’яті вам потрібно округлювати до кратного 4 байти. Якщо змінна знаходиться у глобальному просторі (а не всередині функції), вона також виділить ще 64 байти.

Отже, якщо ви хочете використовувати один з кодів на цій сторінці, ви повинні перевірити, чи результат, використовуючи прості тестові випадки (рядки чи цифри), відповідає цим даним, беручи до уваги кожне із показань у цій публікації (масив $ _GLOBAL, перший виклик функції, буфер виводу, ...)


1
... і це навіть перш ніж потрапити в нутрощі zvalue, is_refа потім скопіювати на записи. Дякую.
Пісквор вийшов з будівлі

1
Завдяки вам я пропустив цю сторінку в Посібнику з PHP. Я додав посилання, щоб завершити свою відповідь (але, мабуть, ви вже це прочитали).
Адам

2

У мене була подібна проблема, і рішення, яке я використав, було написати змінну у файл, а потім запустити на ньому fileize (). Приблизно так (неперевірений код):

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

Це рішення не дуже швидко, оскільки воно включає в себе дисковий вхід, але він повинен дати вам щось набагато точніше, ніж трюки memory_get_usage. Це просто залежить від того, яка точність вам потрібна.


Слід зазначити, що це рішення працює лише для рядкових і одновимірних масивів рядків, і що використовувати strlenбуло б простіше.
Адам


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;
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.