Могу ли я скомпилировать свой PHP-скрипт в более быстрый исполняемый формат? - PullRequest
3 голосов
/ 30 октября 2009

У меня есть PHP-скрипт, который действует как JSON API для моей базы данных.

То есть вы отправляете ему HTTP-запрос вроде: http://example.com/json/?a=1&b=2&c=3..., он вернет объект json с набором результатов из моей базы данных.

PHP отлично подходит для этого, потому что он буквально около 10 строк кода.

Но я также знаю, что PHP медленный, и это API, который иногда вызывается примерно 40 раз в секунду, и PHP изо всех сил старается не отставать.

Есть ли способ, с помощью которого я могу скомпилировать свой PHP-скрипт в более быстрый исполняемый формат? Я уже использую PHP-APC, который является оптимизацией байт-кода для PHP, а также FastCGI.

Или кто-нибудь порекомендует язык, на котором я переписываю скрипт, чтобы Apache все еще мог обрабатывать запросы example.com/json/?

Спасибо

ОБНОВЛЕНИЕ : Я только что провёл несколько тестов:

  • PHP скрипт занимает 0,6 секунды полная
  • Если я использую сгенерированный SQL из приведенного выше сценария PHP и запускаю запрос с того же веб-сервера, но непосредственно из команды MySQL, то есть задержка сети все еще в игре - выборочный результирующий набор занимает всего 0,09 секунды для завершения .

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

Ответы [ 9 ]

8 голосов
/ 30 октября 2009

Прежде чем приступить к оптимизации чего-либо, сначала выясните, если это проблема. Учитывая, что это всего 10 строк кода (по вашему мнению), я очень подозреваю, что у вас нет проблем. Время выполнения скрипта. Имейте в виду, что сетевая задержка обычно сокращает тривиальное время выполнения сценария.

Другими словами: не решайте проблему, пока у вас не возникнет проблема.

Вы уже используете кэш кода операции (APC). Это не становится намного быстрее, чем это. Точнее говоря, редко требуется , чтобы получить что-то более быстрое.

Во всяком случае, у вас будут проблемы с вашей базой данных. Слишком много соединений (маловероятно при скорости 20x в секунду), слишком медленное или большое соединение: запрос слишком медленный. Если вы окажетесь в такой ситуации, 9 из 10 эффективных индексаций и настройки базы данных будет достаточно.

В тех случаях, когда это не так, вы обращаетесь за каким-то видом кэширования: memcached, beanstalkd и т. П.

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

3 голосов
/ 30 октября 2009

Мне очень повезло с совместным использованием PHP, memcached и модуля memcache от nginx для очень быстрых результатов. Самый простой способ - просто использовать полный URL-адрес в качестве ключа кеша

Я приму этот URL:

/widgets.json?a=1&b=2&c=3

Пример PHP-кода:

<?
$widgets_cache_key = $_SERVER['REQUEST_URI'];

// connect to memcache (requires memcache pecl module)
$m = new Memcache;
$m->connect('127.0.0.1', 11211);

// try to get data from cache
$data = $m->get($widgets_cache_key);
if(empty($data)){
    // data is not in cache. grab it.
    $r = mysql_query("SELECT * FROM widgets WHERE ...;");
    while($row = mysql_fetch_assoc($r)){
        $data[] = $row;
    }
    // now store data for next time.
    $m->set($widgets_cache_key, $data);
}

var_dump(json_encode($data));
?>

Это само по себе обеспечивает огромный прирост производительности. Если бы вы затем использовали nginx в качестве внешнего интерфейса для Apache (установите Apache на 8080 и nginx на 80), вы можете сделать это в своей конфигурации nginx:

worker_processes  2;

events {
    worker_connections  1024;
}

http {
    include  mime.types;
    default_type  application/octet-stream;

    access_log  off;
    sendfile  on;
    keepalive_timeout  5;
    tcp_nodelay  on;
    gzip  on;

    upstream apache {
        server  127.0.0.1:8080;
    }

    server {
        listen  80;
        server_name  _;

        location / {
            if ($request_method = POST) {
                proxy_pass  http://apache;
                break;
            }
            set  $memcached_key $uri;
            memcached_pass  127.0.0.1:11211;
            default_type  text/html;
            proxy_intercept_errors  on;
            error_page  404 502 = /fallback;
        }

        location /fallback {
            internal;
            proxy_pass  http://apache;
            break;
        }
    }
}

Обратите внимание на строку set $memcached_key $uri;. Это устанавливает ключ кэша memcached на использование REQUEST_URI, как в сценарии PHP. Так что, если nginx обнаружит запись в кэше с этим ключом, он будет обслуживать ее непосредственно из памяти, и вам никогда не придется трогать PHP или Apache. Очень быстро.

Существует также неофициальный модуль Apache memcache . Еще не пробовал, но если вы не хотите связываться с nginx, это тоже может вам помочь.

1 голос
/ 30 октября 2009

Если ваша база данных очень тяжелая для чтения (я предполагаю, что это так), тогда может помочь базовая реализация кэширования, а memcached сделает ее очень быстрой.

Позвольте мне изменить структуру URL для этого примера:

/widgets.json?a=1&b=2&c=3

Для каждого вызова вашей веб-службы вы сможете анализировать аргументы GET и использовать их для создания ключа для использования в вашем кэше. Давайте предположим, что вы запрашиваете widgets. Пример кода:

<?
// a function to provide a consistent cache key for your resource
function cache_key($type, $params = array()){
 if(empty($type)){
  return false;
 }
 // order your parameters alphabetically by key.
 ksort($params);
 return sha1($type . serialize($params));
}

// you get the same cache key no matter the order of parameters
var_dump(cache_key('widgets', array('a' => 3, 'b' => 7, 'c' => 5)));
var_dump(cache_key('widgets', array('b' => 7, 'a' => 3, 'c' => 5)));


// now let's use some GET parameters.
// you'd probably want to sanitize your $_GET array, however you want.
$_GET = sanitize($_GET);

// assuming URL of /widgets.json?a=1&b=2&c=3 results in the following func call:
$widgets_cache_key = cache_key('widgets', $_GET);

// connect to memcache (requires memcache pecl module)
$m = new Memcache;
$m->connect('127.0.0.1', 11211);

// try to get data from cache
$data = $m->get($widgets_cache_key);
if(empty($data)){
 // data is not in cache. grab it.
 $r = mysql_query("SELECT * FROM widgets WHERE ...;");
 while($row = mysql_fetch_assoc($r)){
  $data[] = $row;
 }
 // now store data for next time.
 $m->set($widgets_cache_key, $data);
}

var_dump(json_encode($data));
?>
1 голос
/ 30 октября 2009

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

PHP не будет вашим узким местом. Я могу в значительной степени гарантировать это. Пропускная способность сети и задержка уменьшат небольшие издержки использования PHP по сравнению с скомпилированной программой на Си. И если не скорость сети, то это будет дисковый ввод-вывод, или доступ к базе данных, или действительно плохой алгоритм, или множество других более вероятных преступников, чем сам язык.

0 голосов
/ 19 марта 2010

С вашей точки зрения, похоже, что php-код действительно является проблемой. Вы можете разместить код?

Что происходит, когда вы удаляете код MySQL и просто вставляете жестко запрограммированную строку, представляющую то, что вы получите от БД?

Поскольку для php требуется 0,60 секунды, а для CLI MySQL - всего 0,09 секунды, я предполагаю, что создание соединения занимает слишком много времени. PHP создает новое соединение для каждого запроса по умолчанию, и это иногда может быть медленным.

Подумайте об этом, в зависимости от вашего env и вашего кода:

  1. Разрешить имя хоста сервера MySQL для IP
  2. Открыть соединение с сервером
  3. Аутентификация на сервере
  4. Наконец запустите ваш запрос

Рассматривали ли вы использование постоянных соединений MySQL или пула соединений ? Это эффективно позволяет вам перейти вправо к шагу запроса сверху.

Кэширование отлично подходит и для производительности. Я думаю, что другие уже достаточно хорошо это охватили.

0 голосов
/ 30 октября 2009

Поскольку у вас уже установлен APC, его можно использовать (аналогично рекомендациям memcached) для хранения объектов. Если вы можете кэшировать результаты своей базы данных, сделайте это! http://us2.php.net/manual/en/function.apc-store.php http://us2.php.net/manual/en/function.apc-fetch.php

0 голосов
/ 30 октября 2009

Пожалуйста, посмотрите этот вопрос . У вас есть несколько вариантов. Да, PHP может быть скомпилирован в нативный формат ELF (и, возможно, даже FatELF). Проблема во всех удобствах зендских существ.

0 голосов
/ 30 октября 2009

Учтите, что если вы работаете с обновлениями баз данных, ваша производительность MySQL - это то, что, IMO, требует внимания. Я бы расширил тестовый жгут так:

  • запустить mytop на сервере базы данных
  • запускать ab (скамейку apache) с клиента, например с рабочего стола
  • запустите top или vmstat на веб-сервере

И следите за этими вещами:

  • обновления таблицы, заставляющие чтения ждать (MyISAM engine)
  • высокая нагрузка на веб-сервер (может указывать на нехватку памяти на веб-сервере)
  • высокая активность диска на веб-сервере, возможно, из-за регистрации или других веб-запросов, вызывающих случайный поиск некэшированных файлов
  • рост памяти ваших процессов Apache. Если ваши результирующие наборы преобразуются в большие ассоциативные массивы или сериализуются / десериализуются, они могут стать дорогостоящими операциями выделения памяти. Возможно, вашему коду нужно избегать вызовов типа mysql_fetch_assoc () и начинать выборку по одной строке за раз.

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

function query( $sql, $dbcon, $thresh ) {
    $delta['begin'] = microtime( true );
    $result = $dbcon->query( $sql );
    $delta['finish'] = microtime( true );
    $delta['t'] = $delta['finish'] - $delta['begin'];
    if( $delta['t'] > $thresh )
        error_log( "query took {$delta['t']} seconds; query: $sql" );
    return $result;
}

Лично я предпочитаю использовать xcache для APC, потому что мне нравится страница диагностики, с которой он идет.

Составьте график вашей работы с течением времени. Отслеживайте количество одновременных подключений и проверяйте, не связано ли это с проблемами производительности. Вы можете получить число http-соединений из netstat из cronjob и зарегистрировать их для последующего анализа.

Также рассмотрите возможность включения кеша запросов MySQL.

0 голосов
/ 30 октября 2009

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

1) Поместите Squid кеширующий прокси перед вашим веб-сервером. Если ваши запросы хорошо кэшируются, это может иметь смысл.

2) Используйте memcached для кэширования дорогостоящих поисков в базе данных.

...