Множественные вызовы jQuery.load() к PHP-скрипту вызывают Невозможно выделить память: не удалось создать ошибку дочернего процесса

У меня есть HTML-страница, которая несколько раз вызывает jQuery.load('test.php'). Результат test.php — «256 КБ», и, как показано ниже, 64 запроса x 256 КБ должны стоить 16 МБ. Однако использование ресурсов cPanel показывает, что одно выполнение 64 запросов на самом деле стоит 154 МБ (или 2,4 МБ на запрос). Несколько циклов превышают лимит виртуальной памяти моего общего хостинга в 1 ГБ и вызывают ошибку:

(12) Невозможно выделить память: не удалось создать дочерний процесс: /opt/suphp/sbin/suphp

Поскольку со статическими файлами этого не происходит, я подозреваю, что у PHP есть некоторые накладные расходы, но насколько? И почему виртуальная память не освобождается сразу после завершения скрипта? Я пробовал "умереть" и "выйти()" безрезультатно. Есть ли способ заставить сборщик мусора PHP (v5.2.17)?

test.html:

<table>
    <tr>
        <td id="column_1"></td>
        <td id="column_2"></td>
        <td id="column_3"></td>
        <td id="column_4"></td>
    </tr>
</table>

<script>
    function loadPage() {
        $('<div>').load('/test.php', function (response) {
            $('#column_1').append(response);
        });
        $('<div>').load('/test.php', function (response) {
            $('#column_2').append(response);
        });
        $('<div>').load('/test.php', function (response) {
            $('#column_3').append(response);
        });
        $('<div>').load('/test.php', function (response) {
            $('#column_4').append(response);
        });
    }
    $(document).ready(function () {
        for ( var j = 0; j < 16; j++ ) {
            loadPage();
        }
    });
</script>

test.php:

echo memory_get_peak_usage(true) / 1024 . 'kB';

(Примечание: на самом деле я не запускаю jQuery.load() 64 раза, но первоначальная ошибка, вызвавшая этот вопрос, была примерно для 12 запросов с размером 5 МБ на запрос. С тех пор я подумал о лучшем способе, который требует меньше запросов, но хочет чтобы гарантировать, что эта ошибка не повторится в рабочей среде, когда приложение будет ожидать получения нескольких одновременных запросов)


person Cosmittus    schedule 28.03.2014    source источник


Ответы (1)


Вывод PHP-скрипта имеет мало общего с памятью, фактически используемой процессом PHP (плюс веб-сервер). Накладные расходы зависят от загруженных модулей и конфигурации сервера; в основном в вашей настройке каждый запрос порождает экземпляр suphp, и даже если он динамически связан (это, не так ли?), многие внутренние структуры будут дублироваться. Я обнаружил, что PHP-модуль Apache более экономичен в использовании памяти (но он также зависит от загруженных модулей, а часто даже не от PHP-модулей, так что проверьте свою конфигурацию, если хотите прыгнуть с корабля).

Вы можете использовать функции отчетов о памяти PHP, чтобы лучше оценить объем памяти для ваших запросов в течение их жизни, а не только в конце, и посмотрите, какие операции потребляют больше всего памяти. Освобождение ненужных объектов с помощью unset() часто бывает полезным.

Вы можете запускать запросы один за другим и сохранять промежуточные результаты в файл на диске, чтобы уменьшить общий объем памяти. Затем последний вызов может собрать результаты и отправить их вместе с одной из функций сквозного доступа.

Отключение буферизации с помощью ob_end_clean() может помочь уменьшить использование памяти серверным процессом (даже если это ничего не может сделать для PHP-части). В вашем случае я не уверен, что это сработает, поскольку процесс на самом деле является независимым двоичным файлом suphp, но вы всегда можете попробовать и посмотреть, что произойдет.

Объединение запросов в большой объект JSON

Если несколько вызовов используют одни и те же входные параметры или «объединяемые» параметры, вы можете попробовать объединить вызовы в один, что также, вероятно, сделает все намного быстрее: вместо того, чтобы возвращать один объект, вы возвращаете несколько объектов, объединенных в один.

Header('Content-Type: application/json');
die(json_encode(array(
    'column1' => 'Output for the call to column1',
    'column2' => ...
));

а в jQuery вы получаете их все и отправляете туда, куда нужно

function loadPage() {
    $.get('/test-big.php', {
        parcol1: 'parameter the code generating column 1'
    }, function(data, textStatus, jqXHR) {
        $('#column_1').append('<div>').html(data.column1);
        $('#column_2').append('<div>').html(data.column2);
        ...
    });
}

Даже если общее время для удовлетворения всех запросов больше, а затем возникает более длительная задержка перед тем, как «что-то произойдет на странице», я считаю, что этот подход почти всегда предпочтительнее по нескольким причинам:

  • это одно HTTP-соединение, экономя накладные расходы на соединение. При конвейерной обработке HTTP это больше не является большим преимуществом, но по-прежнему является преимуществом.
  • функции сжатия, обычно используемые HTTP-серверами, имеют «кривую производительности» и не сжимают, скажем, первый килобайт, а также второй и последующие, особенно если повторений много. Таким образом, отправка пяти пакетов по 2 КБ превращает их в пять пакетов по 0,5 КБ, что в сумме составляет 2,5 КБ. Но на самом деле первый Kb становится 0,3, а второй 0,2; поэтому отправка одного 10-килобайтного пакета превращает его в 0,3+0,2+0,18+0,18+... = может быть, 2 КБ, что составляет 20% экономии без каких-либо затрат.
  • если только запросы полностью не коррелированы и не затрагивают абсолютно разные области вашего приложения, что очень маловероятно, все они будут нести одинаковые затраты на установку (соединения с базой данных, фильтры, буферы, классы загрузки, кэширование). ..). Понести эти расходы только один раз, а не несколько раз, — это приятная сделка.
  • вы используете гораздо меньше ресурсов: не только ОЗУ (одна служебная память против многих), но одно соединение с БД, одно соединение с кешем и так далее и тому подобное. Это правда, что общее использование ресурсов не пропорционально количеству запросов, потому что некоторые из них будут завершены до того, как другие даже начнутся, но таким образом вы уверены, что пропорциональность постоянна. .
  • если запросы очень коррелированы (например, вы запрашиваете общее количество одного столбца запасов для отображения, скажем, графика семафора, и общее количество другого для другого семафора...), тогда вы можете перепроектировать SQL-запрос или что-то еще в чтобы вернуть всю информацию сразу. Вместо SELECT opening, closing FROM nyse WHERE st='XXXX'; для двадцати значений XXXX вы можете сделать SELECT st, opening, closing FROM nyse WHERE st IN ('XXXX1','XXXX2',...); и заменить двадцать запросов одним, который лишь немного медленнее первого. Это не может быть сделано всегда, но это случается достаточно часто, так что это стоит иметь в виду. Правильно создав код jQuery, вы даже сможете отправить все ответы с помощью одного цикла .each (например, если каждый целевой DIV имеет идентификатор, идентичный коду, вы можете отправить JSON, содержащий, скажем, '#XXX1.opening' => 1.23, '#XXX1.closing' => 1.24, ...).
person LSerni    schedule 28.03.2014
comment
Спасибо вам за ваши предложения. Как мне узнать, является ли экземпляр suphp динамически связанным или нет, учитывая, что это разумно ограниченная учетная запись общего хостинга? - person Cosmittus; 29.03.2014
comment
Скорее всего, он динамически связан. Но если вы можете выполнить команду ldd или, может быть, file, вы можете знать наверняка. Попробуйте со страницей, содержащей <?php system("ldd /opt/suphp/sbin/suphp 2>&1"); ?>, и посмотрите, что произойдет. - person LSerni; 29.03.2014
comment
Я не полностью разобрался с этой проблемой, так как 64 вызова PHP за короткий промежуток времени все равно будут забивать доступную виртуальную память, но я не думаю, что на виртуальном хостинге можно что-то сделать. Ответ Iserni принят, потому что он дал мне множество вариантов для смягчения исходной проблемы (и вещь JSON довольно крутая). - person Cosmittus; 31.03.2014