Жизнеспособный способ предсказать использование памяти огромного процесса, после нескольких шагов в цикле - PullRequest
1 голос
/ 10 февраля 2020

У меня есть процесс, использующий много памяти, и этот процесс выполняется внутри al oop, который может иметь что-то вроде 100 итераций. Для каждой итерации процесс потребляет ~ 3 МБ, и сборщик мусора php не может освободить эту память между каждой итерацией.

Сначала я попытался освободить эту утечку памяти (сбросив некоторые выбранные переменных или установив их на null), но напрасно.

Итак, в качестве обходного пути Я бы хотел, чтобы процесс завершился изящно до сбоя в Чтобы сохранить базу данных в чистоте. Поэтому я стараюсь предсказать, как можно точнее, когда пора останавливаться, чтобы предотвратить появление памяти cra sh.

. Для этого я использовал memory_get_usage() и начал отлаживать происходящее. С этим методом я наконец-то увидел, что после каждой итерации около 3 Мбайт памяти протекало. Но когда в памяти происходит cra sh, остается еще около 37 МБ памяти, что, по-видимому, указывает на то, что внутреннему процессу требуется такой объем памяти для работы (~ 37 МБ выделенной памяти, затем только ~ 34 МБ освобождено).

Учитывая, что первые итерации всегда будут работать, как я могу более точно предсказать, сколько реальной памяти необходимо для выполнения одного шага? Я надеялся, что memory_get_peak_usage() даст правильную информацию (например, memory_get_usage() + ~ 37Mb), но странно, возвращаемое значение всегда ниже, чем memory_get_usage().

РЕДАКТИРОВАТЬ: чтобы объяснить это по-другому, моя цель чтобы остановить выполнение до , предел памяти достигнут. Но ограничение фиксированным числом итераций не очень точно: мне нужно догадаться (приблизительно), когда память достигнет критического объема, затем аккуратно остановить выполнение.

$memoryAnalysis = new MemoryAnalysis();
echo "\n";
echo "####### INITIAL MEMORY: ".$memoryAnalysis->getMemoryUsage(true)."\n";
echo "####### INITIAL MEMORY PEAK: ".$memoryAnalysis->getMemoryPeakUsage(true)."\n";
echo "\n";

foreach ($mails as $n => $mail) {

    // ... PERFORM HUGE PROCESS ...

    $memoryAnalysis->newStep();
    echo "\n";
    echo "####### LOOP $n\n";
    echo "####### Memory: ".$memoryAnalysis->getMemoryUsage(true)."\n";
    echo "####### Memory peak: ".$memoryAnalysis->getMemoryPeakUsage(true)."\n";
    echo "####### Average memory per step: ".$memoryAnalysis->getMemoryUsagePerStep(true)."\n";
    echo "####### Average memory peak per step: ".$memoryAnalysis->getMemoryPeakUsagePerStep(true)."\n";
}

Вот код для MemoryAnalysis класса:

class MemoryAnalysis
{
    /**
     * @var int
     */
    protected $nbSteps;
    /**
     * @var int
     */
    protected $initialMemoryUsage;
    /**
     * @var int
     */
    protected $initialMemoryPeakUsage;

    /**
     * MemoryAnalysis constructor.
     */
    public function __construct()
    {
        $this->nbSteps = 0;
        $this->initialMemoryUsage = memory_get_usage();
        $this->initialMemoryPeakUsage = memory_get_peak_usage();
    }

    /**
     * Add a new step after a huge memory use has been done.
     *
     * @return void
     */
    public function newStep(): void
    {
        $this->nbSteps++;
    }

    /**
     * Return the amount of used memory since the creation of the objecT.
     *
     * @param bool $formatted Return a formatted string instead of an integer
     *
     * @return int|string
     */
    public function getMemoryUsage(bool $formatted = false)
    {
        $mem = memory_get_usage() - $this->initialMemoryUsage;

        return $formatted ? self::format($mem) : $mem;
    }

    /**
     * Return the average memory usage per step.
     *
     * @param bool $formatted Return a formatted string instead of an integer
     *
     * @return int|string|null Null if no step was added
     */
    public function getMemoryUsagePerStep(bool $formatted = false)
    {
        if ($this->nbSteps < 0) {
            return null;
        }

        $mem = $this->getMemoryUsage() / $this->nbSteps;

        return $formatted ? self::format($mem) : $mem;
    }

    /**
     * Return the amount of used memory since the creation of the objecT.
     *
     * @param bool $formatted Return a formatted string instead of an integer
     *
     * @return int|string
     */
    public function getMemoryPeakUsage(bool $formatted = false)
    {
        $mem = memory_get_peak_usage() - $this->initialMemoryPeakUsage;

        return $formatted ? self::format($mem) : $mem;
    }

    /**
     * Return the average memory usage per step.
     *
     * @param bool $formatted Return a formatted string instead of an integer
     *
     * @return int|string|null Null if no step was added
     */
    public function getMemoryPeakUsagePerStep(bool $formatted = false)
    {
        if ($this->nbSteps === 0) {
            return null;
        }
        $mem = $this->getMemoryPeakUsage() / $this->nbSteps;

        return $formatted ? self::format($mem) : $mem;
    }

    /**
     * Format a long number in a readable way.
     *
     * @param int $n Number
     *
     * @return string String separated by commas
     */
    public static function format(int $n): string
    {
        $n = (string) $n;
        $len = strlen($n);
        $nbGroups = (int) floor(($len - 1) / 3);
        $output = '';
        for ($i = 0; $i !== $nbGroups + 1; $i++) {
            $start = $len - ($i + 1) * 3;
            $groupLen = min(3, $start + 3);
            $start = max(0, $start);
            $group = substr($n, $start, $groupLen);
            $output = ',' . $group . $output;
        }

        return substr($output, 1);
    }
}

И вот вывод, который я получаю:

####### INITIAL MEMORY: 408
####### INITIAL MEMORY PEAK: 0

####### LOOP 0
####### Memory: 15,222,912
####### Memory peak: 15,347,416
####### Average memory per step: 15,222,912
####### Average memory peak per step: 15,347,416

####### LOOP 1
####### Memory: 18,449,320
####### Memory peak: 18,506,312
####### Average memory per step: 9,224,660
####### Average memory peak per step: 9,253,156

####### LOOP 2
####### Memory: 21,043,056
####### Memory peak: 21,073,976
####### Average memory per step: 7,014,352
####### Average memory peak per step: 7,024,658

####### LOOP 3
####### Memory: 23,620,488
####### Memory peak: 23,629,968
####### Average memory per step: 5,905,122
####### Average memory peak per step: 5,907,492

[...]

####### LOOP 30
####### Memory: 89,779,456
####### Memory peak: 89,837,232
####### Average memory per step: 2,896,111
####### Average memory peak per step: 2,897,975

####### LOOP 31
####### Memory: 97,670,280
####### Memory peak: 97,713,624
####### Average memory per step: 3,052,196
####### Average memory peak per step: 3,053,550

Здесь l oop ломается и показывает следующую ошибку:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/project/vendor/symfony/options-resolver/OptionsResolver.php on line 805
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32768 bytes) in /var/www/project/vendor/symfony/debug/Exception/OutOfMemoryException.php on line 1

ПРИМЕЧАНИЕ : среднее потребление памяти может быть более точным, если пропустить первый шаг, который занимает действительно больше памяти, чем следующие. Но мой вопрос больше об этом «невидимом» использовании памяти размером ~ 37 МБ в примере, который я не знаю, как предсказать.

РЕДАКТИРОВАТЬ 2 : благодаря предоставленным советам по комментариям sintakonte потребление памяти было разделено на 3. Но вопрос остается открытым.

...