PHP闭包给出了奇怪的性能行为

时间:2012-07-01 00:41:25

标签: php performance closures anonymous-function

今天早些时候我正在开发一个PHP 5.3+应用程序,这意味着我可以自由使用PHP闭包。太棒了,我想!然后我遇到了一段代码,其中使用功能性PHP代码会使事情变得更容易,但是,虽然我有一个合乎逻辑的答案,但它让我想知道在{{1}内直接调用闭包之间的性能影响是什么并将其作为变量传递下去。即以下两个:

array_map()

$test_array = array('test', 'test', 'test', 'test', 'test' );
array_map( function ( $item ) { return $item; }, $test_array );

正如我所想,后者确实更快,但差别并不大。实际上,重复这些相同的测试10000次并取平均值的差异为0.05秒。甚至可能是侥幸。

这让我更加好奇。那么$test_array = array('test', 'test', 'test', 'test', 'test' ); $fn = function ( $item ) { return $item; }; array_map( $fn, $test_array ); 和闭包怎么样?同样,经验告诉我create_function()create_function()之类的东西应该更慢,因为它创建一个函数,对其进行评估,然后再存储它。 而且,正如我所想的那样,array_map()确实更慢。这都是create_function()

然后,我不知道为什么我这样做,但是我做了,我检查了array_map()和闭包之间的区别,同时保存它并只调用一次。没有处理,没有任何东西,只是简单地传递一个字符串,并返回该字符串。

测试成了:

create_function()

$fn = function($item) { return $item; };
$fn('test');

我分别运行了10000次这些测试,并查看了结果并获得了平均值。我对结果感到非常惊讶。

事实证明,此次关闭的速度约为4倍。这不是我想的。我的意思是,通过$fn = create_function( '$item', 'return $item;' ); $fn('test'); 运行闭包要快得多,并且通过array_map()变量运行相同的函数甚至更快,这与此测试几乎相同。

结果

array_map()

好奇为什么要这样做,我检查了CPU使用率和其他系统资源并确保没有必要运行,一切都很好,所以我再次运行测试,但我得到了类似的结果。

所以我只尝试了一次相同的测试,然后多次运行(每次当然都计时)。事实证明闭合确实慢了4倍,除了偶尔它会比array 0 => array 'test' => string 'Closure test' (length=12) 'iterations' => int 10000 'time' => float 5.1327705383301E-6 1 => array 'test' => string 'Anonymous test' (length=14) 'iterations' => int 10000 'time' => float 1.6745710372925E-5 快两到三倍,我猜这只是吸虫,但它似乎足以削减我做了1000次测试的时间缩短了一半。

以下是我用于进行这些测试的代码。谁能告诉我这到底是怎么回事?这是我的代码还是只是PHP代理?

create_function()

此代码的结果:

<?php

/**
 * Simple class to benchmark code
 */
class Benchmark
{
    /**
     * This will contain the results of the benchmarks.
     * There is no distinction between averages and just one runs
     */
    private $_results = array();

    /**
     * Disable PHP's time limit and PHP's memory limit!
     * These benchmarks may take some resources
     */
    public function __construct() {
        set_time_limit( 0 );
        ini_set('memory_limit', '1024M');
    }

    /**
     * The function that times a piece of code
     * @param string $name Name of the test. Must not have been used before
     * @param callable|closure $callback A callback for the code to run.
     * @param boolean|integer $multiple optional How many times should the code be run,
     * if false, only once, else run it $multiple times, and store the average as the benchmark
     * @return Benchmark $this
     */
    public function time( $name, $callback, $multiple = false )
    {
        if($multiple === false) {
            // run and time the test
            $start = microtime( true );
            $callback();
            $end = microtime( true );

            // add the results to the results array
            $this->_results[] = array(
                'test' => $name,
                'iterations' => 1,
                'time' => $end - $start
            );
        } else {
            // set a default if $multiple is set to true
            if($multiple === true) {
                $multiple = 10000;
            }

            // run the test $multiple times and time it every time
            $total_time = 0;
            for($i=1;$i<=$multiple;$i++) {
                $start = microtime( true );
                $callback();
                $end = microtime( true );
                $total_time += $end - $start;
            }
            // calculate the average and add it to the results
            $this->_results[] = array(
                'test' => $name,
                'iterations' => $multiple,
                'time' => $total_time/$multiple
            );
        }
        return $this; //chainability
    }

    /**
     * Returns all the results
     * @return array $results
     */
    public function get_results()
    {
        return $this->_results;
    }
}

$benchmark = new Benchmark();

$benchmark->time( 'Closure test', function () {
    $fn = function($item) { return $item; };
    $fn('test');
}, true);

$benchmark->time( 'Anonymous test', function () {
    $fn = create_function( '$item', 'return $item;' );
    $fn('test');
}, true);

$benchmark->time( 'Closure direct', function () {
    $test_array = array('test', 'test', 'test', 'test', 'test' );
    $test_array = array_map( function ( $item ) { return $item; }, $test_array );
}, true);

$benchmark->time( 'Closure stored', function () {
    $test_array = array('test', 'test', 'test', 'test', 'test' );
    $fn = function ( $item ) { return $item; };
    $test_array = array_map( $fn, $test_array );
}, true);

$benchmark->time( 'Anonymous direct', function () {
    $test_array = array('test', 'test', 'test', 'test', 'test' );
    $test_array = array_map( create_function( '$item', 'return $item;' ), $test_array );
}, true);

$benchmark->time( 'Anonymous stored', function () {
    $test_array = array('test', 'test', 'test', 'test', 'test' );
    $fn = create_function( '$item', 'return $item;' );
    $test_array = array_map( $fn, $test_array );
}, true);

var_dump($benchmark->get_results());

2 个答案:

答案 0 :(得分:16)

5.1327705383301E-6不比1.6745710372925E-5慢4倍;它快3倍左右。你正在读错号码。似乎在所有结果中,闭包始终比create_function更快。

答案 1 :(得分:2)

参见此基准:

<?php
$iter = 100000;


$start = microtime(true);
for ($i = 0; $i < $iter; $i++) {}
$end = microtime(true) - $start;
echo "Loop overhead: ".PHP_EOL;
echo "$end seconds".PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < $iter; $i++) {
    $fn = function($item) { return $item; };
    $fn('test');
}
$end = microtime(true) - $start;
echo "Lambda function: ".PHP_EOL;
echo "$end seconds".PHP_EOL;


$start = microtime(true);
for ($i = 0; $i < $iter; $i++) {
    $fn = create_function( '$item', 'return $item;' );
    $fn('test');
}
$end = microtime(true) - $start;
echo "Eval create function: ".PHP_EOL;
echo "$end seconds".PHP_EOL;

结果:

Loop overhead: 
0.011878967285156 seconds
Lambda function: 
0.067019939422607 seconds
Eval create function: 
1.5625419616699 seconds

现在,有趣的是,如果将函数声明放在 for for循环之外:

Loop overhead: 
0.0057950019836426 seconds
Lambda function: 
0.030204057693481 seconds
Eval create function: 
0.040947198867798 seconds

回答你原来的问题,将lambda函数赋值给变量并简单地使用它没有什么区别。除非您多次使用它,否则使用变量会更好地代码清晰度