Производительность PHP exec()

Следующий код PHP возвращает мне время выполнения около 3,5 секунд (измеряется несколько раз и усредняется):

$starttime = microtime(true);
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;

Когда я запускаю ту же команду через терминал ssh, время выполнения уменьшается примерно до 0,6 секунд (измеряется с помощью инструмента командной строки time).

Версия библиотеки imagemagick:

Version: ImageMagick 6.7.0-10 2012-12-18 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2011 ImageMagick Studio LLC
Features: OpenMP

В чем может быть причина такой разницы во времени?

Один из ответов на аналогичный вопрос здесь о stackru заключался в том, что накладные расходы связаны с тем, что веб-серверу нужно запустить поток / оболочку. Может ли это быть действительно причиной? Я думал, что потоки легковесны и не требуют много времени, чтобы начать / закончить.

До звонка exec я установил число потоков, используемых imagemagick (потому что это была / есть ошибка в OpenMP?, ссылка) на 1 с exec('env MAGICK_THREAD_LIMIT=1');, Время выполнения от PHP не сильно меняется, независимо от того, какое значение я установил MAGICK_THREAD_LIMIT, В любом случае, в этой версии, похоже, нет ошибки в OpenMP, потому что время выполнения командной строки в порядке.

Будем весьма благодарны за любые предложения о том, как я могу улучшить время выполнения вышеуказанной команды.

Большое спасибо за Вашу помощь.

6 ответов

Решение

Когда вы входите на компьютер Unix, либо с клавиатуры, либо через ssh, вы создаете новый экземпляр оболочки. Оболочка обычно что-то вроде /bin/sh или же /bin/bash, Оболочка позволяет выполнять команды.

Когда вы используете exec()также создает новый экземпляр оболочки. Этот экземпляр выполняет команды, которые вы ему отправили, и затем завершает работу.

Когда вы создаете новый экземпляр команды оболочки, он имеет свои собственные переменные окружения. Итак, если вы сделаете это:

exec('env MAGICK_THREAD_LIMIT=1');
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');

Затем вы создаете две оболочки, и параметр в первой оболочке никогда не попадает во вторую оболочку. Чтобы получить переменную окружения во второй оболочке, вам нужно что-то вроде этого:

exec('env MAGICK_THREAD_LIMIT=1; /usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');

Теперь, если вы считаете, что проблема может заключаться в самой оболочке, потому что создание оболочки занимает слишком много времени, протестируйте ее с помощью чего-то, что, как вы знаете, почти не занимает времени:

$starttime = microtime(true);
exec('echo hi');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;

В этот момент вы знаете, как попытаться найти способ ускорить создание оболочки.

Надеюсь это поможет!

Я программирую на компьютерах более 56 лет, но я впервые столкнулся с такой ошибкой. Таким образом, я потратил почти неделю, пытаясь понять, в 7 раз хуже скорость выполнения при выполнении perl-программы из php через exec по сравнению с выполнением perl-программы непосредственно в командной строке. В рамках этой работы я также изучал все случаи, когда эта проблема поднималась в Интернете. Вот что я нашел: (1) Это ошибка, о которой впервые сообщили в 2002 году, и она не была исправлена ​​в последующие 11 лет. (2) Ошибка связана с тем, как apache взаимодействует с php, поэтому обе эти организации перекладывают ответственность на другую. (3) Ошибка одинакова для exec, system или любой другой альтернативы. (4) Ошибка не зависит от того, является ли execed-программа perl, exe или чем-то еще. (5) Ошибка одинакова в UNIX и Windows. (6) Ошибка не имеет ничего общего с imagemagick или изображениями в целом. Я столкнулся с ошибкой в ​​совершенно другой обстановке. (7) Ошибка не имеет никакого отношения ко времени запуска fork, shell, bash, что угодно. (8) Ошибка не устраняется путем смены владельца службы apache. (9) Я не уверен, но я думаю, что это связано со значительным увеличением накладных расходов при вызове подпрограмм. Когда я столкнулся с этой проблемой, у меня была программа на Perl, которая выполнялась за 40 секунд, но через exec потребовалось 304 секунды. Мое окончательное решение состояло в том, чтобы выяснить, как оптимизировать мою программу так, чтобы она выполнялась через 0,5 секунды напрямую или через 3,5 секунды через exec. Так что я никогда не решал проблему.

@ Филипп, так как у вас есть SSH и так как ваш сервер разрешает доступ к exec() Я предполагаю, что у вас также есть полный root-доступ к машине.

Рекомендуется для обработки одного файла

Наличие root-доступа к машине означает, что вы можете изменить /etc/php5/php.ini настройки предела памяти.

Даже без прямого доступа к /etc/php5/php.ini Вы можете проверить, поддерживает ли ваш сервер переопределение php.ini директивы, создавая новый php.ini файл в каталоге ваших проектов.

Даже если переопределения не разрешены, вы можете изменить настройки памяти с .htaccess если AllowOverride является All,

Еще один способ изменить ограничение памяти - установить его во время выполнения PHP, используя ini_set('memory_limit', 256);,

Рекомендуется для пакетной обработки файлов

Единственная хорошая вещь о запуске конвертации через exec() если вы не планируете получить результат от exec() и позволяя ему работать асинхронно:

exec('convert --your-convert-options > /dev/null 2>/dev/null &');

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

Примечания по производительности

Используя код выше, чтобы сделать exec бежать async для обработки одного файла потребуется больше процессорного времени и больше памяти, чем в GD/Imagick в PHP. Время / память будут использоваться другим процессом, который не влияет на процесс PHP (заставляя посетителей чувствовать, что сайт движется быстрее), но потребление памяти существует, и когда дело доходит до обработки многих соединений, которые будут учитываться.

Это не ошибка PHP и не имеет ничего общего с Apache/Nginx или любым веб-сервером.

В последнее время у меня возникла та же проблема, и я посмотрел исходный код PHP, чтобы проверить реализацию exec().

По сути, PHP exec() вызывает функцию Liben popen ().

Нарушитель C's popen() выглядит очень медленно. Быстрый поиск в Google по "c popen slow" покажет вам много вопросов, таких как ваш.

Я также обнаружил, что кто-то реализовал функцию с именем popen_noshell() в C, чтобы преодолеть эту проблему производительности:

https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/

Вот скриншот, показывающий разницу в скорости против popen () и popen_noshell():

Разница в скорости

PHP exec() использует обычный popen () - тот, что справа на скриншоте выше. Процессор, используемый системой при выполнении popen () в C, очень высок, как вы можете видеть.

Я вижу 2 решения этой проблемы:

  1. Создать расширение PHP, которое реализует popen_noshell
  2. Запрос от команды PHP на создание нового набора функций popen_noshell(), exec_noshell () и т. Д., Что вряд ли произойдет, я думаю...

Дополнительное примечание:

В процессе поиска я обнаружил функцию PHP с тем же именем, что и у C: popen ()

Это интересно, потому что можно выполнить внешнюю команду асинхронно с помощью: pclose(popen('your command', 'r'));

Который по сути имеет тот же эффект, что и exec('ваша команда &');

Я столкнулся с этой проблемой, команда графической обработки, которая при запуске через командную строку занимала около 0,025 секунды, а при вызове через exec() в PHP занимала около 0,3 секунды. После долгих исследований кажется, что большинство людей считают, что это проблема с Apache или PHP. Затем я попытался запустить команду через CGI-скрипт, полностью обойдя PHP, и получил тот же результат.

Казалось, поэтому проблема должна быть apache, поэтому я установил lighttpd, и получил тот же результат!

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

exec('echo "password" | sudo -S nice -n -20 command')

ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: Я знаю, что на это будут всевозможные возражения по поводу безопасности. Я просто хотел сосредоточиться на том ответе, что все, что вам нужно сделать, это добавить nice перед вашей командой.

Когда вы звоните exec php не создает поток, он создает новый дочерний процесс. Создание нового процесса требует больших затрат.

Однако когда вы соединяетесь с ssh, вы просто передаете команду для выполнения. Вы не являетесь владельцем этой программы, поэтому она выполняется как пользователь, с которым вы связались. За exec это пользователь, который запускает PHP.

Другие вопросы по тегам