【问题标题】:Improve performance of php on local server提高本地服务器上 php 的性能
【发布时间】:2011-09-01 02:05:48
【问题描述】:

我有一个 XAMPP 安装,几乎是默认配置。

一般来说,性能不是什么大问题,因为我主要使用 PHP 来运行网页和小型 Web 应用程序。等待几秒钟的页面并不罕见。

但是,我最近处理了 Project Euler 的问题,并决定在 PHP 中解决这些问题。

尽我所能,我无法让我的代码在不到 1 分 1 秒的时间内运行(从近 3 分钟优化),我感到非常尴尬,尤其是考虑到 Pjt Euler 上的大多数海报报告的时间为 1 -3 秒。 (#7,找到第 10001 个素数)

我将我的代码移植到 C# 中,同样的任务一眨眼就完成了。 0.4 秒。相同的算法,代码中唯一显着的区别是我在 C# 中使用了一个 List 来替换我在 PHP 中使用的数组。

虽然我确实期望 C# 的性能优于 php,但这种差异让我怀疑存在严重的配置问题,但我不知道该去哪里找。

性能不佳的原因可能是什么?


编辑:这是代码:

在 PHP 中:

/*
  * Project Euler #7:
  * By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.
  * What is the 10001st prime number?
  */

ini_set('max_execution_time', 300);  
echo "start time:" . date("i:s:u") . "<br />";
function isPrime($number, $prevPrimes)
{       
    foreach ($prevPrimes as $key =>$prime)
    {
        if ($prime == 1)
        {
            continue;
        }
        elseif ($number % $prime == 0)
        {
            return 0;
        }
    }
    // If we get to here, $number is prime
    return $number; 
}
$primes = array();
$i = 0;
$nbPrimes = 0;
while ($nbPrimes <10001)
{
    $i++;
    if ($i % 2 != 0)
    {
        $result = isPrime($i, $primes);

        if ($result != 0)
        {
            $primes[] = $i;
            $nbPrimes++;
        }
    }
}
echo "#$nbPrimes: $result<br>";

echo "End time:" . date("i:s:u") . "<br />";

在 C# 中:

public static void RunSnippet()
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    List<int> primes = new List<int>();
    int i = 0;
    int nbPrimes = 0;
    int result =0;
    while (nbPrimes <10001)
    {
        i++;
        if (i % 2 != 0)
        {
            result = isPrime(i, primes);

            if (result != 0)
            {
                primes.Add(i);
                nbPrimes++;
            }
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Time elapsed: {0}",
    stopwatch.Elapsed);
    Console.WriteLine ("#" + nbPrimes + ": " + result.ToString());
}
public static int isPrime(int number, List<int> prevPrimes)
{
    foreach (int prime in prevPrimes)
    {
        if (prime == 1)
        {
            continue;
        }
        else if (number % prime == 0)
        {
            return 0;
        }
    }
    // If we get to here, number is prime
    return number;  
}   

【问题讨论】:

  • @G Molvi:我认为这不属于服务器故障。 @Sylverdrag,我刚刚通过 PHP CLI 在我的 Linux 上测试了代码(PHP 版本),是的,它大约需要 30 秒,或者需要 5 秒的执行时间。将 apache 排除在等式之外可能会带来更好的时间,但我也没有运行 XAMP。就 C# 而言,它做得更快,它是一种编译语言,但余量相当大,是的,我有兴趣看看 PHP 是否可以更接近它。
  • @Sylverdrag 您可以通过将 == 更改为 === 并主要通过将 $prevPrimes 作为参考 pastebin.com/C2z7vUj5 传递来略微提高性能
  • @Sylverdrag 不要提及它 - 应该有人发布它:D 啊,nvm,Brad F Jacobs 需要积分(j/k)
  • @Salman A:那更糟。从 6 秒缩短到 36 秒。
  • @Salman 我见过的基准测试将 foreach 列为表现最好的数组迭代循环。

标签: php performance xampp


【解决方案1】:

最终编辑

这是来自 Bakudan 逻辑的 PHP 代码,它返回以下结果:

start time:44:25:000000
#10001: 104759
End time:44:26:000000

守则:

<?php
echo "start time:" . date("i:s:u") . "\n";

function isPrime($number, &$primes)
{
    if ($number === 1) return false;
    elseif ($number %2 === 0) return false;
    elseif ($number < 4) return true;
    elseif ($number < 9) return true;
    elseif ($number %3 === 0) return false;
    else $r = floor(sqrt($number));

    $f = 5;
    while ($f <= $r) {
        if ($number % $f ===0) return false;
        if ($number % ($f+2) === 0) return false;
        $f = $f + 6;
    }

    return true;
}

$primes = array();
$nbPrimes = $i = 0;
while ($nbPrimes < 10001)
{
    $i++;
    if (isPrime($i, $primes) !== false)
    {
        $primes[] = $i;
        $nbPrimes++;
    }
}
echo "#$nbPrimes: " . end($primes) . "\n";
echo "End time:" . date("i:s:u") . "\n";

Bakudan给了我伪代码,我只是为上面的OP的脚本翻译并写出来的。


编辑 2

我稍微清理了代码,没有改进任何东西,可能会增强“可读性”。但是,是的,我认为这是您使用 PHP 可以获得的最佳效果,在没有 apache 的 i7 上产生 5 秒。

    <?php
    echo "start time:" . date("i:s:u") . "\n";

    function isPrime($number, &$primes)
    {
        foreach($primes as $prime) {
            if ($number % $prime === 0 && $prime > 1)
                    return false;
        }
    }

    $primes = array();
    $nbPrimes = $i = 1;
    while ($nbPrimes <= 10001)
    {
        if ($i % 2 !== 0 && isPrime($i, $primes) !== false)
        {
            $primes[] = $i;
            $nbPrimes++;
        }
        $i++;
    }
    echo "#$nbPrimes: " . end($primes) . "\n";
    echo "End time:" . date("i:s:u") . "\n";

编辑

通过将$prime === 1 移动到$number % $prime 之后再检查同一if 语句。

start time:29:40:000000
#10001: 104743
End time:29:45:000000

采纳 Hannes 建议的严格检查并将数组作为参考传递,并添加我自己的一些调整(修改函数内部的数组):

ini_set('max_execution_time', 300);
echo "start time:" . date("i:s:u") . "\n";

function isPrime($number, &$prevPrimes)
{
   foreach ($prevPrimes as $prime) {
        if ($number % $prime === 0 && $prime !== 1)
        {
            return false;
        }
    }

    // If we get to here, $number is prime
    $prevPrimes[] = $number;
    return $number;
}

$primes = array();
$i = 0;
$nbPrimes = 0;
while ($nbPrimes < 10001)
{
    $i++;
    if ($i % 2 !== 0)
    {
        $result = isPrime($i, $primes);

        if ($result !== 0)
        {
            $nbPrimes++;
        }
    }
}
echo "#$nbPrimes: $result\n";

echo "End time:" . date("i:s:u") . "\n";

最终变成:

start time:52:08:000000
#10001: 104743
End time:52:15:000000

VS 你的代码:

start time:50:44:000000
#10001: 104743
End time:51:17:000000

那里有一个很好的改进,但不像 C#,只是展示了编译语言的力量 :)

【讨论】:

  • 谢谢。将 $prime === 1 与 "if" 结合起来是一个很好的优化。现在减少到 23 秒(通过 Apache)。在 C# 中,它还略微提高了速度(0.41s =>0.40s),而通过引用传递给出了相同的结果(可能已经由编译器优化)。在批准答案之前,我将等待一天左右,以防万一有其他优化建议。到目前为止,这个问题对我来说是一个金矿。
  • 不用担心。它只是引起了我的兴趣,我觉得它很有趣 :) 很高兴它有帮助,我仍在努力改进它,但已经没有想法了。如果我偶然发现更多内容,我会更新。
【解决方案2】:

数学的“使用force ...”!只是抛出一些毫无意义的代码。这里只是可以提高性能的几点。

  • 为什么要使用数组来匹配数字?
  • foreach 函数因此无效 - 循环应在 floor(sqrt(number)) 结束

    示例:sqrt(64) = 8 -> 所有素数除法器都是从 1 到 8。其他的将是它们的乘积(32 = 4 x 8 = 2x2x2x2x2)

  • 使用公式跳转到下一个可能的素数

    数学:

    可被 2 整除的数字 - 2, 4, 6, 8, 10, 12 -> 2k+1 = 2x1+1 = 3, 5, .....

    可被 3 - 3 整除的数字,6, 9, 12 -> 我们已经有了 6 和 12,所以 3, 9, 15, 21 -> 3( 2k-1) = 3(2x1-1) = 3, 9, ...

这里有一些 pseudo code 来自 euler 项目的 hk 管理员

isPrime ( number )
{
    if ( number == 1 )      return false
    elseif ( number < 4 )       return true
    elseif ( number % 2 == 0 )  return false
    elseif ( number < 9 )       return true
    elseif ( number % 3 == 0 )  return false
    else
        r = floor ( sqrt ( number ) ) 
    f = 5
    while ( f <= r )
    {
        if ( number % f == 0 ) return false
        if ( number % ( f + 2 ) == 0 ) return false
        f = f + 6
    }
    return true
}

PS

关于执行速度的差异 - PHP 是解释性语言,要在浏览器中查看结果,您有 3 个程序正在运行 - 浏览器、服务器、php 解释器。你发出一个 http 请求,服务器调用 php(可能还有一堆其他的东西,例如日志记录),php 读取脚本并执行它。步骤比 C# 多得多。

在 C# 中执行编译的代码。

【讨论】:

  • @Bakudan:谢谢。我会调查的。但是,这里真正的问题是 php 中的这段代码与 C# 中的相同代码之间的性能差异。我并不特别担心找到计算素数的最有效方法,我有兴趣找出是什么让 PHP 执行得更快或至少不那么慢。
  • @Bakudan:谢谢。据我了解,请求是从浏览器发送到 Apache 服务器的。然后将请求发送到 php 解释器。然后 PHP 解释器运行脚本(包括计时器调用),然后将结果发送回 Apache,Apache 将它们发送到浏览器。换句话说,计时只发生在 php 解释器上,不包括 http 往返所需的时间。如果这是正确的,我看不到浏览器-Apache 序列将如何增加脚本的运行时间。我错过了 PHP 在 wamp 堆栈上运行的方式中的一些重要内容吗?
  • @Sylverdrag 我的意思是运行 3 个程序时,您的内存使用量和处理器时间使用量会更多。
  • @Bakudan:哦。但是对于 c# 脚本也是如此。运行相同的程序。无论哪种方式,CPU或内存都没有完全使用,所以它不应该是限制。 PHP 会导致相当大的峰值,但它不会在任何内核上达到最大值。 C# 甚至没有注册
  • @Slyverdrag,C# 不必通过这些程序。它们可能在后台运行,但 C# 不依赖它们并且不使用它们。像 C# 一样编译程序会带来巨大的改进,因为 PHP 是一种脚本语言,而不是编译的。所以它的代码每次运行都必须由编译器翻译。与 C# 一样,它只需要翻译一次。编译 PHP 有很多方法,我相信 HipHop (facebook.com/note.php?note_id=280583813919) 是一种方法。您可以沿着这条路冒险,看看是否会有所作为。
【解决方案3】:

虽然我确实期望 C# 表现出色 php,这种差异导致我 怀疑存在严重的配置问题, 但我不知道去哪里找。

启动 PHP 引擎会给网络服务器带来一点开销。 PHP 的加载方式(例如,在服务器启动时作为模块加载或为每个 .php 请求按需加载)决定了所涉及的开销。那么在windows上有两种可用的PHP变种:线程安全的和非线程安全的,后者声称更快。

如果是 XAMPP 配置问题,我认为您可以通过在网络服务器上运行 3 次测试并记下平均时间来隔离它。然后通过 PHP CLI 运行相同的脚本 3 次并记下平均值。如果差异很明显,那么您可能会责怪 XAMPP。您应该能够在 XAMPP 安装文件夹中的某处找到 PHP CLI 二进制文件。

在我的系统上,我得到以下结果:

PHP-CLI:            #10001: 104743 -- Time taken: 30.25 second(s)
PHP on IIS/FastCGI: #10001: 104743 -- Time taken: 29.89 second(s)
PHP on Apache/CGI:  #10001: 104743 -- Time taken: 29.93 second(s)

差别不大——我宁愿优化代码。

编辑

使用此修改后的代码,除了执行时间之外,同一台机器和所有内容都从大约 30 秒缩短到了大约 5.85 秒。唯一值得一提的是,我使用了一个全局数组,而不是每次调用 isPrime 函数时都按值传递它(准确地说是 104743 次)。通过引用传递数组也会导致类似的执行时间,给或需要 1 秒。比较运算符只减少一两秒,但不多。

/*
 * Project Euler #7:
 * By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.
 * What is the 10001st prime number?
 */
ini_set('max_execution_time', 300);  
$t0 = microtime(true);
$primes = array();
function isPrime($number)
{       
    global $primes;
    foreach ($primes as $prime)
    {
        if ($prime === 1)
        {
            continue;
        }
        elseif ($number % $prime === 0)
        {
            return 0;
        }
    }
    return $number; 
}
$i = 0;
$nbPrimes = 0;
while ($nbPrimes < 10001)
{
    $i++;
    if ($i % 2 !== 0)
    {
        $result = isPrime($i);
        if ($result !== 0)
        {
            $primes[] = $i;
            $nbPrimes++;
        }
    }
}
$t1 = microtime(true);
echo sprintf('#%d: %d -- Time taken: %.2f second(s)', $nbPrimes, $result, $t1 - $t0);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-17
    • 2014-10-08
    • 1970-01-01
    • 2011-10-12
    • 2021-04-18
    • 1970-01-01
    • 2013-04-18
    • 1970-01-01
    相关资源
    最近更新 更多