PHP 中的惰性函数定义 - 可能吗?

问题描述 投票:0回答:9

在 JavaScript 中,您可以使用 Lazy Function Definitions 来优化对函数的第 2-N 次调用,方法是仅在第一次调用函数时执行昂贵的一次性操作。

我想在 PHP 5 中做同样的事情,但是不允许重新定义函数,也不允许重载函数。

实际上我想做的就是如下所示,只是进行了优化,以便第 2 - N 个调用(例如 25-100)不需要重新检查它们是否是第一个调用。

$called = false;
function foo($param_1){
  global $called;
  if($called == false){
    doExpensiveStuff($param_1);
    $called = true;
  }
  echo '<b>'.$param_1.'</b>';
}

PS 我曾考虑过使用 include_once() 或 require_once() 作为函数中的第一行来仅执行一次外部代码,但我听说这些也很昂贵。

有什么想法吗?或者有更好的方法来解决这个问题吗?

php design-patterns optimization
9个回答
13
投票

使用本地静态变量:

function foo() {
    static $called = false;
    if ($called == false) {
        $called = true;
        expensive_stuff();
    }
}

避免为此使用全局变量。它使全局命名空间变得混乱,并使函数的封装性降低。如果除了函数内部之外的其他地方需要知道它是否被调用,那么将这个函数放在像 Alan Storm 指出的类中是值得的。


6
投票

你真的分析过这段代码吗?我怀疑额外的布尔测试是否会对页面渲染时间产生任何可测量的影响。


4
投票

您可以进行条件函数定义。

if( !function_exists('baz') )
{ 
    function baz( $args ){ 
        echo $args; 
    }
}

但目前,函数一旦定义就变成了一块砖头。

你可以使用

create_function
,但我建议你不要,因为它很慢,占用大量内存,直到 php 退出才得到 free(),并且是一个与 eval() 一样大的安全漏洞。

等到 PHP5.3,我们有了“闭包”http://wiki.php.net/rfc/closures

然后你就可以做

if( !isset( $baz ) ) 
 { 
    $baz = function( $args )
    { 
        echo $args;
    }
}

$baz('hello');

$baz = function( $args )
{ 
       echo $args + "world"; 
}
$baz('hello');

进一步阅读,这就是你想要的效果。

$fname = 'f_first'; 
function f_first( $even ) 
{ 
    global $fname; 
    doExpensiveStuff(); 
    $fname = 'f_others';
    $fname( $even );
    /* code */ 
}
function f_others( $odd ) 
{
     print "<b>".$odd."</b>";
}

foreach( $blah as $i=>$v ) 
{
   $fname($v);
}

它会做你想做的事,但调用可能比普通函数调用贵一点。

在 PHP5.3 中这也应该有效:

$func = function( $x ) use ( $func ) 
{ 
     doexpensive(); 
     $func = function( $y )
     { 
          print "<b>".$y."</b>";
     }
     $func($x);
}
foreach( range(1..200) as $i=>$v ) 
{ 
    $func( $v ); 
}

(就我个人而言,我当然认为所有这些巧妙的技巧都会比您之前对 2 个正位进行的比较慢得多。;))

如果您真的关心在任何地方都获得最佳速度

$data = // some array structure
doslowthing(); 
foreach( $data as $i => $v ) 
{
   // code here 
}

但是,您可能无法做到这一点,但您没有给出足够的范围来澄清。但是,如果您能做到这一点,那么简单的答案通常是最好的:)


1
投票

请不要使用

include()
include_once()
,除非您不在乎
include()
是否会失败。如果您包含代码,那么您会关心。始终使用
require_once()


0
投票

如果您最终发现额外的布尔测试成本太高,您可以将变量设置为函数的名称并调用它:

$func = "foo";    

function foo()
{
    global $func;
    $func = "bar";
    echo "expensive stuff";
};


function bar()
{
    echo "do nothing, i guess";
};

for($i=0; $i<5; $i++)
{
    $func();
}

尝试一下


0
投票

PHP 没有词法作用域,因此您无法使用函数执行您想要的操作。然而,PHP 有类,从概念上讲,它们的工作方式与此目的完全相同。

在 javascript 中,你会这样做:

var cache = null;
function doStuff() {
  if (cache == null) {
    cache = doExpensiveStuff();
  }
  return cache;
}

使用类(在 PHP 中),你会这样做:

class StuffDoer {
  function doStuff() {
    if ($this->cache == null) {
      $this->cache = $this->doExpensiveStuff();
    }
    return $this->cache;
  }
}

是的,基于类的 oop 比函数式编程更冗长,但在性能方面它们应该差不多。

除此之外,PHP 5.3 可能会获得词法作用域/闭包支持,因此当它出现时,您可以用更流畅的函数式编程风格进行编写。请参阅 PHP rfc-wiki 以获取此功能的详细描述


0
投票

使用局部静态变量怎么样?

function doStuff($param1) {
    static $called = false;
    if (!$called) {
        doExpensiveStuff($param1);
        $called = true;
    }
    // do the rest
}

如果您只需要对给定的参数值执行一次昂贵的操作,您可以使用数组缓冲区:

function doStuff($param1) {
    static $buffer = array();
    if (!array_key_exists($param1, $buffer)) {
        doExpensiveStuff($param1);
        $buffer[$param1] = true;
    }
    // do the rest
}

局部静态变量在函数调用之间保持不变。他们记住返回后的价值。


0
投票

您致力于功能性风格模式的原因是什么?尽管有匿名函数和关闭计划,PHP 实际上并不是一种函数式语言。看起来类和对象是更好的解决方案。

Class SomeClass{
    protected $whatever_called;
    function __construct(){
        $this->called = false;
    }
    public function whatever(){
        if(!$this->whatever_called){
            //expensive stuff
            $this->whatever_called = true;
        }
        //rest of the function
    }
} 

如果你想变得更奇特,你可以使用魔术方法来避免预先定义被调用的布尔值。如果您不想实例化对象,请使用静态。


0
投票

在 PHP 8.3 中我使用的是:

    class Helper
    {
        /**
         * @template T
         * @param callable(): T $callback
         * @return callable(): T
         */
        public static function lazy(callable $callback): callable
        {
            return function () use ($callback) {
                static $run = true;
                static $cache = null;
                if ($run) {
                    $cache = $callback();
                    $run = false;
                }
                return $cache;
            };
        }
    }

用法如下:

$c = 0;
// pass any task that should only be performend once and/or only if you need it, like:
$x = Helper::lazy(fn() => $c);

$y = $x();
echo "0 $y\n"; // 0 0

$c = 1;
$y = $x();
echo "1 $y\n"; // 1 0
© www.soinside.com 2019 - 2024. All rights reserved.