【问题标题】:Can I compile my PHP script to a faster executing format?我可以将我的 PHP 脚本编译成更快的执行格式吗?
【发布时间】:2009-10-30 03:18:41
【问题描述】:

我有一个 PHP 脚本,它充当我的后端数据库的 JSON API。

意思是,你向它发送一个 HTTP 请求,例如:http://example.com/json/?a=1&b=2&c=3... 它会返回一个 json 对象,其中包含我数据库中的结果集。

PHP 非常适合这个,因为它实际上只有大约 10 行代码。

但我也知道 PHP 很慢,而且这是一个有时每秒被调用 40 次的 API,而 PHP 正在努力跟上。

有没有办法可以将我的 PHP 脚本编译成更快的执行格式?我已经在使用 PHP-APC,它是 PHP 和 FastCGI 的字节码优化。

或者,有没有人推荐一种我重写脚本的语言,以便 Apache 仍然可以处理 example.com/json/ 请求?

谢谢

更新:我刚刚运行了一些基准测试:

  • PHP 脚本需要 0.6 秒 完成
  • 如果我使用从上面的 PHP 脚本生成的 SQL 并从同一个 Web 服务器但直接从 MySQL 命令中运行查询,这意味着网络延迟仍在发挥作用 - 获取的结果集只需 0.09 秒即可完成.

如您所见,PHP 在生成结果方面确实慢了 1 个数量级。在这种情况下,网络似乎不是主要瓶颈,尽管我同意它通常是根本原因。

【问题讨论】:

  • 您能否提供您正在运行的代码行(如果只有 10 行)?
  • 因为它只有 10 行 PHP,我也有兴趣看到它。我很难相信运行 10 行 php 需要 0.6 秒。我猜 Web 服务器配置可能是问题所在,但如果不了解您的基础架构/源的更多信息,就很难说。

标签: php optimization compilation fastcgi


【解决方案1】:

在优化某些东西之前,首先要弄清楚它是否存在问题。考虑到它只有 10 行代码(根据你的说法),我非常怀疑你没有问题。脚本执行所需的时间。请记住,网络延迟通常会使琐碎的脚本执行时间相形见绌。

换句话说:在遇到问题之前不要解决问题。

您已经在使用操作码缓存 (APC)。没有比这更快的了。更重要的是,它很少需要比这更快。

如果您的数据库有任何问题。连接太多(不太可能以每秒 20 倍的速度),连接太慢或连接过大:查询太慢。如果您发现自己在这种情况下 10 次中有 9 次有效的索引和数据库调整就足够了。

如果不是,您可以使用某种缓存:memcached、beantalkd 等。

但老实说,每秒 20 倍意味着这些解决方案几乎可以肯定地过度设计了一些不成问题的东西。

【讨论】:

  • 同意。如果您从数据库查询返回结果,也许是查询本身很慢?尝试在 SQL 服务器上分析查询。
  • 好评。简而言之,执行速度更快的格式 APC APC APC APC
【解决方案2】:

我很幸运地同时使用了 PHP、memcached 和 nginx 的 memcache 模块以获得非常快的结果。最简单的方法是只使用完整的 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 module。没试过,但如果你不想弄乱 nginx,这也可能对你有帮助。

【讨论】:

  • 为什么只推荐在内存缓存键上使用 md5 或 sha1。
【解决方案3】:

优化的第一条规则是确保您确实遇到了性能问题。第二条规则是通过测量您的代码找出性能问题出在哪里。不要猜测。进行严格的测量。

PHP 不会成为您的瓶颈。我几乎可以保证。网络带宽和延迟将使使用 PHP 与编译的 C 程序的小开销相形见绌。如果不是网络速度,那么它将是磁盘 I/O、数据库访问、或者一个非常糟糕的算法,或者比语言本身更可能的罪魁祸首。

【讨论】:

  • 我运行了一个基准测试,请参阅上面的更新帖子。在这种情况下,网络和 I/O 不会成为瓶颈
【解决方案4】:

如果您的数据库读取量很大(我猜是这样),那么基本的缓存实现会有所帮助,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));
?>

【讨论】:

    【解决方案5】:

    您已经在使用 APC 操作码缓存,这很好。如果您发现仍无法获得所需的性能,您可以尝试以下其他方法:

    1) 在您的网络服务器前面放置一个Squid caching proxy。如果您的请求是高度可缓存的,这可能很有意义。

    2) 使用memcached 缓存昂贵的数据库查找。

    【讨论】:

    • 我运行了一个基准测试,请参阅上面的更新帖子。在这种情况下,网络和 I/O 不会成为瓶颈
    【解决方案6】:

    考虑一下,如果您正在处理数据库更新,那么 IMO 需要注意您的 MySQL 性能。我会像这样扩展测试工具:

    • 在 dbserver 上运行 mytop
    • 从客户端运行 ab(apache bench),例如您的桌面
    • 在网络服务器上运行 top 或 vmstat

    并注意这些事情:

    • 更新表强制读取等待(MyISAM 引擎)
    • 网络服务器负载高(可能表明网络服务器内存不足)
    • 网络服务器上的高磁盘活动,可能来自日志记录或其他网络请求,导致随机查找未缓存的文件
    • 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,因为我喜欢它附带的诊断页面。

    绘制一段时间内的表现。跟踪并发连接的数量,看看这是否与性能问题相关。您可以从 cronjob 中 grep 来自 netstat 的 http 连接数并将其记录下来以供稍后分析。

    考虑也启用您的 mysql 查询缓存。

    【讨论】:

      【解决方案7】:

      请参阅this question。你有几个选择。是的,PHP 可以编译为原生 ELF(甚至可能是 FatELF)格式。问题在于 Zend 的所有生物舒适度。

      【讨论】:

        【解决方案8】:

        由于您已经安装了 APC,因此可以使用它(类似于 memcached 建议)来存储对象。如果您可以缓存数据库结果,那就去做吧! http://us2.php.net/manual/en/function.apc-store.php http://us2.php.net/manual/en/function.apc-fetch.php

        【讨论】:

          【解决方案9】:

          从您的基准测试看来,php 代码确实是问题所在。可以发一下代码吗?

          当您删除 MySQL 代码并只放入一个硬编码字符串来表示您将从数据库返回的内容时会发生什么?

          由于从 php 需要 0.60 秒,而从 MySQL CLI 只需要 0.09 秒,我猜想连接创建花费了太多时间。 PHP 默认为每个请求创建一个新连接,有时会很慢。

          考虑一下,根据您的环境和代码,您将:

          1. 将 MySQL 服务器的主机名解析为 IP
          2. 打开与服务器的连接
          3. 对服务器进行身份验证
          4. 最后运行您的查询

          您是否考虑过使用persistent MySQL connectionsconnection pooling? 它有效地允许您从上面直接跳转到查询步骤。

          缓存对性能也很有帮助。我认为其他人已经很好地涵盖了这一点。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-06
            • 1970-01-01
            • 1970-01-01
            • 2012-01-13
            • 2011-04-12
            相关资源
            最近更新 更多