提高本地服务器上 php 的性能

发布于 2024-11-09 11:22:10 字数 2469 浏览 0 评论 0 原文

我安装了 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;  
}   

I have a XAMPP install, with pretty much the default config.

Performance isn't much of a problem in general as I use PHP mostly to run web pages and small web apps. Waiting a couple seconds for a page is not unusual.

However, I have recently taken up the problems from Project Euler and decided to do them in PHP.

Try as I may, I couldn't get my code to run in less than 1 minute 1 second (optimized down from almost 3 min) and I was getting pretty embarrassed, especially considering most posters on Pjt Euler reported times of 1-3 seconds. (#7, find the 10001th prime)

I ported my code to C#, and the same task completed in a blink. 0.4 seconds. Same algorithm, the only notable difference in the code is that I used a List in C# to replace the array I was using in PHP.

While I did expect C# to outperform php, this difference leads me to suspect a gross configuration problem, but I have no idea where to look.

What could be the cause of this poor performance?


Edit: Here is the code:

In 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 />";

In 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;  
}   

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

单身狗的梦 2024-11-16 11:22:10

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

  • 为什么你使用数组来匹配数字?
  • 因此 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, ...

这里有一些 来自 href="http://projecteuler.net/profile/hk.png" rel="nofollow">hk admin at project euler

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# 中,执行编译后的代码。

"Use the force ..." of math! Just throwing some code pointless. Here are just a few points that can boost the performance.

  • why you are using array to match the number against?
  • the foreach function is thus ineffective - the cycle should end at floor(sqrt(number))

    example: sqrt(64) = 8 -> all prime dividers will be from 1 to 8. The others will be product of them( 32 = 4 x 8 = 2x2x2x2x2 )

  • use formulas to jump to the next possibly prime number

    math:

    numbers divisable by 2 - 2, 4, 6, 8, 10, 12 -> 2k+1 = 2x1+1 = 3, 5, .....

    numbers divisable by 3 - 3, 6, 9, 12 -> we already have 6 and 12, so 3, 9, 15, 21 -> 3(2k-1) = 3(2x1-1) = 3, 9, ...

here is some pseudo code from hk admin at project euler

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

About the difference in the speed of the execution - PHP is interpreted language, to view the result in browser you have 3 programs running - browser, server, php interpreter. You make a http request, the server calls php (and probably a bunch of other stuff, logging for example),php reads the script and executes it. There are much more steps than in C#.

In C# the compiled code is executed.

夜还是长夜 2024-11-16 11:22:10

最终编辑

这是来自 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# 那样的东西展示编译语言的力量:)

FINAL EDIT

Here is the PHP code from Bakudan's logic, which returns this result:

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

The Code:

<?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 gave me the pseudo code, I Just translated and wrote it out for the OP's script above.


EDIT 2

I cleaned up the code a bit, didn't improve anything, may enhance "readability". But yea, I think this is the best you will get with PHP, which on an i7 without apache yields 5 seconds.

    <?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";

EDIT

Knocked another second off by moving the $prime === 1 to be after the $number % $prime check in the same if statement.

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

Taking Hannes suggestion of strict checking and passing the array as reference plus adding a few tweaks of my own (modifying the array inside the function):

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";

Which ended up being:

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

VS your code:

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

A good improvement there, but nothing like C#, just goes to show the power of a compiled language :)

四叶草在未来唯美盛开 2024-11-16 11:22:10

虽然我确实期望 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);

While I did expect C# to outperform
php, this difference leads me to
suspect a gross configuration problem,
but I have no idea where to look.

Firing the PHP engine creates a little overhead for the webserver. The way PHP is loaded (e.g. loaded as a module on server startup or loaded on demand for every .php request) determines how much overhead is involved. Then on windows there are two variants of PHP available: thread-safe and non thread-safe, the latter one is claimed to be faster.

If its a XAMPP configuration problem, I think you can isolate it by running the test 3 times on your webserver and note down the average time. Then run the same script via PHP CLI 3 times and note down the average. If the difference is noticeable then you might blame XAMPP. You should be able to locate the PHP CLI binary somewhere inside the XAMPP installation folder.

On my system I get these results:

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)

Not much of a difference -- I would rather optimize the code.

EDIT

Same machine and everything but execution time brought down from ~30 seconds to ~5.85 seconds with this revised code. The only thing worth mentioning is that that I used a global array instead of passing it by value every time the isPrime function is called (104743 times to be precise). Passing the array by reference also results in similar execution time, give or take 1 second. The comparison operators shave off just a second or two but not much.

/*
 * 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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文