2008-11-16 33 views
52

Hiện tại tôi đang xem xét việc sử dụng các lớp ReflectionClass và ReflectionMethod trong khung MVC của riêng mình, vì tôi cần tự động instanciate các lớp điều khiển và gọi các phương thức của chúng mà không cần cấu hình bất kỳ. qua cách tiếp cận "cấu hình").PHP 5 Reflection API performance

Tôi lo ngại về hiệu suất, mặc dù tôi nghĩ rằng các yêu cầu cơ sở dữ liệu có khả năng bị tắc nghẽn lớn hơn mã PHP thực tế.

Vì vậy, tôi tự hỏi liệu có ai có trải nghiệm tốt hay xấu với PHP 5 Reflection từ quan điểm thực hiện.

Bên cạnh đó, tôi rất tò mò muốn biết liệu có bất kỳ khung công tác PHP phổ biến nào (CI, Cake, Symfony, v.v.) thực sự sử dụng Reflection hay không.

+0

tôi sử dụng phản ánh trong Europa (http://europaphp.org, http://github.com/treshugart/EuropaPHP) và nó là nhanh hơn so với tất cả những. – Tres

+0

Zend Framework 1.x sử dụng Reflection trong Bootstrapping. Vì ZF cho phép các hàm '_init' được gọi tự động, nó cần một số cơ chế cho việc này. Và nó sử dụng Reflection. –

+0

Laravel sử dụng Reflection khá một chút. –

Trả lời

46

Đừng lo lắng. Cài đặt Xdebug và đảm bảo nút cổ chai ở đâu.

Có chi phí để sử dụng sự phản chiếu, nhưng liệu vấn đề đó có phụ thuộc vào những gì bạn đang làm hay không. Nếu bạn thực hiện điều khiển/yêu cầu dispatcher bằng cách sử dụng Reflection, sau đó nó chỉ là một sử dụng cho mỗi yêu cầu. Hoàn toàn không đáng kể.

Nếu bạn triển khai lớp ORM bằng phản xạ, hãy sử dụng nó cho mọi đối tượng hoặc thậm chí mọi quyền truy cập vào một thuộc tính và tạo hàng trăm hoặc hàng nghìn đối tượng thì có thể tốn kém.

+1

Cảm ơn, tôi không biết về Xdebug. Nó trông giống như một công cụ tuyệt vời. Lớp ORM của tôi không nên sử dụng sự phản chiếu chút nào, tôi sẽ chỉ sử dụng nó một lần cho người điều phối yêu cầu của tôi. Tôi nghĩ rằng bạn đang đúng về nó là không đáng kể! – Franck

4

Chi phí nhỏ nên không có hình phạt hiệu suất lớn nào khác như db, xử lý mẫu, vv là các vấn đề về hiệu năng, kiểm tra khung của bạn bằng hành động đơn giản để xem tốc độ nhanh như thế nào.

Ví dụ đoạn code dưới đây (frontcontroller) trong đó sử dụng phản ánh thực hiện nó việc làm trong một vài mili giây

<?php 
require_once('sanitize.inc'); 

/** 
* MVC Controller 
* 
* This Class implements MVC Controller part 
* 
* @package MVC 
* @subpackage Controller 
* 
*/ 
class Controller { 

    /** 
    * Standard Controller constructor 
    */ 
    static private $moduleName; 
    static private $actionName; 
    static private $params; 

    /** 
    * Don't allow construction of the controller (this is a singleton) 
    * 
    */ 
    private function __construct() { 

    } 

    /** 
    * Don't allow cloning of the controller (this is a singleton) 
    * 
    */ 
    private function __clone() { 

    } 

    /** 
    * Returns current module name 
    * 
    * @return string 
    */ 
    function getModuleName() { 
     return self :: $moduleName; 
    } 

    /** 
    * Returns current module name 
    * 
    * @return string 
    */ 
    function getActionName() { 
     return self :: $actionName; 
    } 

    /** 
    * Returns the subdomain of the request 
    * 
    * @return string 
    */ 
    function getSubdomain() { 
     return substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], '.')); 
    } 

    function getParameters($moduleName = false, $actionName = false) { 
     if ($moduleName === false or ($moduleName === self :: $moduleName and $actionName === self :: $actionName)) { 
      return self :: $params; 
     } else { 
      if ($actionName === false) { 
       return false; 
      } else { 
       @include_once (FRAMEWORK_PATH . '/modules/' . $moduleName . '.php'); 
       $method = new ReflectionMethod('mod_' . $moduleName, $actionName); 
       foreach ($method->getParameters() as $parameter) { 
        $parameters[$parameter->getName()] = null; 
       } 
       return $parameters; 
      } 
     } 
    } 

    /** 
    * Redirect or direct to a action or default module action and parameters 
    * it has the ability to http redirect to the specified action 
    * internally used to direct to action 
    * 
    * @param string $moduleName 
    * @param string $actionName 
    * @param array $parameters 
    * @param bool $http_redirect 

    * @return bool 
    */ 
    function redirect($moduleName, $actionName, $parameters = null, $http_redirect = false) { 
     self :: $moduleName = $moduleName; 
     self :: $actionName = $actionName; 
     // We assume all will be ok 
     $ok = true; 

     @include_once (PATH . '/modules/' . $moduleName . '.php'); 

     // We check if the module's class really exists 
     if (!class_exists('mod_' . $moduleName, false)) { // if the module does not exist route to module main 
      @include_once (PATH . '/modules/main.php'); 
      $modClassName = 'mod_main'; 
      $module = new $modClassName(); 
      if (method_exists($module, $moduleName)) { 
       self :: $moduleName = 'main'; 
       self :: $actionName = $moduleName; 
       //$_PARAMS = explode('/' , $_SERVER['REQUEST_URI']); 
       //unset($parameters[0]); 
       //$parameters = array_slice($_PARAMS, 1, -1); 
       $parameters = array_merge(array($actionName), $parameters); //add first parameter 
      } else { 
       $parameters = array($moduleName, $actionName) + $parameters; 
       $actionName = 'index'; 
       $moduleName = 'main'; 
       self :: $moduleName = $moduleName; 
       self :: $actionName = $actionName; 
      } 
     } else { //if the action does not exist route to action index 
      @include_once (PATH . '/modules/' . $moduleName . '.php'); 
      $modClassName = 'mod_' . $moduleName; 
      $module = new $modClassName(); 
      if (!method_exists($module, $actionName)) { 
       $parameters = array_merge(array($actionName), $parameters); //add first parameter 
       $actionName = 'index'; 
      } 
      self :: $moduleName = $moduleName; 
      self :: $actionName = $actionName; 
     } 
     if (empty($module)) { 
      $modClassName = 'mod_' . self :: $moduleName; 
      $module = new $modClassName(); 
     } 

     $method = new ReflectionMethod('mod_' . self :: $moduleName, self :: $actionName); 

     //sanitize and set method variables 
     if (is_array($parameters)) { 
      foreach ($method->getParameters() as $parameter) { 
       $param = current($parameters); 
       next($parameters); 
       if ($parameter->isDefaultValueAvailable()) { 
        if ($param !== false) { 
         self :: $params[$parameter->getName()] = sanitizeOne(urldecode(trim($param)), $parameter->getDefaultValue()); 
        } else { 
         self :: $params[$parameter->getName()] = null; 
        } 
       } else { 
        if ($param !== false) {//check if variable is set, avoid notice 
         self :: $params[$parameter->getName()] = sanitizeOne(urldecode(trim($param)), 'str'); 
        } else { 
         self :: $params[$parameter->getName()] = null; 
        } 
       } 
      } 
     } else { 
      foreach ($method->getParameters() as $parameter) { 
       self :: $params[$parameter->getName()] = null; 
      } 
     } 

     if ($http_redirect === false) {//no redirecting just call the action 
      if (is_array(self :: $params)) { 
       $method->invokeArgs($module, self :: $params); 
      } else { 
       $method->invoke($module); 
      } 
     } else { 
      //generate the link to action 
      if (is_array($parameters)) { // pass parameters 
       $link = '/' . $moduleName . '/' . $actionName . '/' . implode('/', self :: $params); 
      } else { 
       $link = '/' . $moduleName . '/' . $actionName; 
      } 
      //redirect browser 
      header('Location:' . $link); 

      //if the browser does not support redirecting then provide a link to the action 
      die('Your browser does not support redirect please click here <a href="' . $link . '">' . $link . '</a>'); 
     } 
     return $ok; 
    } 

    /** 
    * Redirects to action contained within current module 
    */ 
    function redirectAction($actionName, $parameters) { 
     self :: $actionName = $actionName; 
     call_user_func_array(array(&$this, $actionName), $parameters); 
    } 

    public function module($moduleName) { 
     self :: redirect($moduleName, $actionName, $parameters, $http_redirect = false); 
    } 

    /** 
    * Processes the client's REQUEST_URI and handles module loading/unloading and action calling 
    * 
    * @return bool 
    */ 
    public function dispatch() { 
     if ($_SERVER['REQUEST_URI'][strlen($_SERVER['REQUEST_URI']) - 1] !== '/') { 
      $_SERVER['REQUEST_URI'] .= '/'; //add end slash for safety (if missing) 
     } 

     //$_SERVER['REQUEST_URI'] = @str_replace(BASE ,'', $_SERVER['REQUEST_URI']); 
     // We divide the request into 'module' and 'action' and save paramaters into $_PARAMS 
     if ($_SERVER['REQUEST_URI'] != '/') { 
      $_PARAMS = explode('/', $_SERVER['REQUEST_URI']); 

      $moduleName = $_PARAMS[1]; //get module name 
      $actionName = $_PARAMS[2]; //get action 
      unset($_PARAMS[count($_PARAMS) - 1]); //delete last 
      unset($_PARAMS[0]); 
      unset($_PARAMS[1]); 
      unset($_PARAMS[2]); 
     } else { 
      $_PARAMS = null; 
     } 

     if (empty($actionName)) { 
      $actionName = 'index'; //use default index action 
     } 

     if (empty($moduleName)) { 
      $moduleName = 'main'; //use default main module 
     } 
     /* if (isset($_PARAMS)) 

      { 

      $_PARAMS = array_slice($_PARAMS, 3, -1);//delete action and module from array and pass only parameters 

      } */ 
     return self :: redirect($moduleName, $actionName, $_PARAMS); 
    } 
} 
+0

Vì vậy, nhiều không gian màu trắng làm cho nó khó khăn để làm theo mã của bạn. –

5

Bên cạnh đó, tôi tò mò muốn được biết nếu có một trong những frameworks PHP phổ biến (CI, Bánh, Symfony, v.v.) thực sự sử dụng Phản ánh.

http://framework.zend.com/manual/en/zend.server.reflection.html

"Thông thường, chức năng này sẽ chỉ được sử dụng bởi các nhà phát triển của các lớp máy chủ cho khuôn khổ."

18

Gọi hàm tĩnh 1 triệu lần sẽ mất ~ 0,31 giây trên máy của tôi. Khi sử dụng một ReflectionMethod, nó tốn ~ 1,82 giây. Điều đó có nghĩa là nó đắt hơn 500% để sử dụng API phản chiếu.

Đây là mã tôi đã sử dụng bằng cách này:

<?PHP 

class test 
{ 
    static function f(){ 
      return; 
    } 
} 

$s = microtime(true); 
for ($i=0; $i<1000000; $i++) 
{ 
    test::f('x'); 
} 
echo ($a=microtime(true) - $s)."\n"; 

$s = microtime(true); 
for ($i=0; $i<1000000; $i++) 
{ 
    $rm = new ReflectionMethod('test', 'f'); 
    $rm->invokeArgs(null, array('f')); 
} 

echo ($b=microtime(true) - $s)."\n"; 

echo 100/$a*$b; 

Rõ ràng, tác động thực tế phụ thuộc vào số lượng các cuộc gọi mà bạn mong đợi để làm

+17

Nó có thể đắt hơn 500%, nhưng nó vẫn trung bình chỉ 1,82 micro giây cho mỗi cuộc gọi. –

+4

Thử nghiệm này không chính xác, vì thể hiện phương pháp phản chiếu chỉ được tạo một lần. Không có trong chu kỳ. – lisachenko

+0

@Alexander: Kiểm tra điểm chuẩn của tôi. –

1

CodeIgniter defenitly sử dụng Reflections. Và tôi đặt cược những người khác cũng làm. Nhìn vào lớp Controller trong thư mục system/controller trong khi cài đặt ci.

2

Trong trường hợp của tôi, nó chỉ chậm hơn 230% so với gọi phương thức lớp trực tiếp, nhanh như hàm call_user_func.

+0

Trong tiêu chuẩn của tôi (tôi đã đăng nó) ReflectionMethod là * gần như * nhanh như 'call_user_func', mặc dù nó chỉ chậm hơn từ 200% - 220% so với gọi trực tiếp phương thức. –

2

Đôi khi sử dụng một cái gì đó như call_user_func_array() có thể giúp bạn có được những gì bạn cần.Không biết hiệu suất khác nhau như thế nào.

52

tôi benchmarked những 3 lựa chọn (điểm chuẩn khác không phải là tách chu kỳ CPU và là 4Y cũ):

class foo { 
    public static function bar() { 
     return __METHOD__; 
    } 
} 

function directCall() { 
    return foo::bar($_SERVER['REQUEST_TIME']); 
} 

function variableCall() { 
    return call_user_func(array('foo', 'bar'), $_SERVER['REQUEST_TIME']); 
} 

function reflectedCall() { 
    return (new ReflectionMethod('foo', 'bar'))->invoke(null, $_SERVER['REQUEST_TIME']); 
} 

Thời gian tuyệt đối thực hiện cho 1.000.000 lặp:

print_r (Benchmark (mảng ('directCall', 'variableCall', 'reflectCall'), 1000000));

Array 
(
    [directCall] => 4.13348770 
    [variableCall] => 6.82747173 
    [reflectedCall] => 8.67534351 
) 

Và thời gian tương đối, còn với 1.000.000 lặp (chạy riêng biệt):

ph() -> Dump (Benchmark (array ('directCall', 'variableCall', ' reflectCall '), 1000000, true));

Array 
(
    [directCall] => 1.00000000 
    [variableCall] => 1.67164707 
    [reflectedCall] => 2.13174915 
) 

Có vẻ như rằng hiệu suất phản ánh được tăng lên rất nhiều trong 5.4.7 (từ ~ 500% xuống ~ 213%).

Đây là Benchmark() chức năng tôi đã sử dụng nếu có ai muốn chạy lại chuẩn này:

function Benchmark($callbacks, $iterations = 100, $relative = false) 
{ 
    set_time_limit(0); 

    if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0) 
    { 
     $result = array_fill_keys($callbacks, 0); 
     $arguments = array_slice(func_get_args(), 3); 

     for ($i = 0; $i < $iterations; ++$i) 
     { 
      foreach ($result as $key => $value) 
      { 
       $value = microtime(true); 
       call_user_func_array($key, $arguments); 
       $result[$key] += microtime(true) - $value; 
      } 
     } 

     asort($result, SORT_NUMERIC); 

     foreach (array_reverse($result) as $key => $value) 
     { 
      if ($relative === true) 
      { 
       $value /= reset($result); 
      } 

      $result[$key] = number_format($value, 8, '.', ''); 
     } 

     return $result; 
    } 

    return false; 
} 
+1

+1 cho phản ứng hùng hồn và chi tiết được cung cấp. – onalbi

+1

+1 để trở nên tuyệt vời. Bạn quên đề cập đến phiên bản PHP. Hãy biểu thị điều đó. –

+0

Thử nghiệm trên PHP 7.1.7: 'mảng (3) { [ "directCall"] => string (10) "1,00000000" [ "variableCall"] => string (10) "1,06057096" [" reflectedCall "] => chuỗi (10)" 2.59103844 " }' – Ryall

1

dựa trên mã mà @Alix Axel cung cấp

Vì vậy, cho đầy đủ, tôi quyết định quấn mỗi tùy chọn trong một lớp và bao gồm bộ nhớ đệm của các đối tượng nếu có. đây là kết quả và mã Các kết quả trên PHP 5.6 trên một i7-4710HQ

array (
    'Direct' => '5.18932366', 
    'Variable' => '5.62969398', 
    'Reflective' => '6.59285069', 
    'User' => '7.40568614', 
) 

Code:

function Benchmark($callbacks, $iterations = 100, $relative = false) 
{ 
    set_time_limit(0); 

    if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0) 
    { 
     $result = array_fill_keys(array_keys($callbacks), 0); 
     $arguments = array_slice(func_get_args(), 3); 

     for ($i = 0; $i < $iterations; ++$i) 
     { 
      foreach ($result as $key => $value) 
      { 
       $value = microtime(true); call_user_func_array($callbacks[$key], $arguments); $result[$key] += microtime(true) - $value; 
      } 
     } 

     asort($result, SORT_NUMERIC); 

     foreach (array_reverse($result) as $key => $value) 
     { 
      if ($relative === true) 
      { 
       $value /= reset($result); 
      } 

      $result[$key] = number_format($value, 8, '.', ''); 
     } 

     return $result; 
    } 

    return false; 
} 

class foo { 
    public static function bar() { 
     return __METHOD__; 
    } 
} 

class TesterDirect { 
    public function test() { 
     return foo::bar($_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterVariable { 
    private $class = 'foo'; 

    public function test() { 
     $class = $this->class; 

     return $class::bar($_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterUser { 
    private $method = array('foo', 'bar'); 

    public function test() { 
     return call_user_func($this->method, $_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterReflective { 
    private $class = 'foo'; 
    private $reflectionMethod; 

    public function __construct() { 
     $this->reflectionMethod = new ReflectionMethod($this->class, 'bar'); 
    } 

    public function test() { 
     return $this->reflectionMethod->invoke(null, $_SERVER['REQUEST_TIME']); 
    } 
} 

$testerDirect = new TesterDirect(); 
$testerVariable = new TesterVariable(); 
$testerUser = new TesterUser(); 
$testerReflective = new TesterReflective(); 

fputs(STDOUT, var_export(Benchmark(array(
    'Direct' => array($testerDirect, 'test'), 
    'Variable' => array($testerVariable, 'test'), 
    'User' => array($testerUser, 'test'), 
    'Reflective' => array($testerReflective, 'test') 
), 10000000), true)); 
0

tôi muốn một cái gì đó mới hơn, vì vậy hãy xem this repo. Từ tóm tắt:

  • PHP 7 là gần gấp đôi nhanh như PHP 5 trong trường hợp phản xạ - Đây không trực tiếp chỉ ra rằng phản xạ là nhanh hơn trên PHP7, các PHP7 lõi ​​đã vừa nhận được một tối ưu hóa tuyệt vời và tất cả mã sẽ hưởng lợi từ điều này.
  • Phản ánh cơ bản khá nhanh - Phương pháp đọc và nhận xét tài liệu cho 1000 lớp học chỉ mất vài phần nghìn giây. Phân tích/Tự động tải các lớp học mất nhiều thời gian hơn so với cơ chế phản chiếu thực tế . Trên hệ thống kiểm tra của chúng tôi phải mất khoảng 300ms để tải 1000 lớp tệp vào bộ nhớ (yêu cầu/bao gồm/tự động tải) - Và chỉ cần 1-5ms đến sử dụng phân tích phản ánh (chú thích doc, getMethods, v.v ...) trên cùng số tiền các lớp học.
  • Kết luận: Phản xạ nhanh và trong các trường hợp sử dụng thông thường, bạn có thể bỏ qua tác động hiệu suất đó. Tuy nhiên, chúng tôi luôn khuyên bạn chỉ nên phân tích cú pháp những gì cần thiết. Và, phản ánh bộ nhớ đệm không cung cấp cho bạn bạn bất kỳ lợi ích đáng chú ý nào về hiệu suất.

Ngoài ra, hãy kiểm tra another benchmark.

Kết quả thu được trên máy OS X phát triển bằng cách sử dụng PHP 5.5.5. [...]

  • Đọc một thuộc tính duy nhất trên một đối tượng: Đóng cửa nhanh hơn một chút.

  • Đọc một thuộc tính duy nhất trên nhiều đối tượng: Phản ánh nhanh hơn.

  • Đọc tất cả các thuộc tính của đối tượng: Đóng cửa nhanh hơn.

  • Viết một thuộc tính duy nhất trên một đối tượng: Phản xạ nhanh hơn một chút.

  • Viết một thuộc tính duy nhất trên nhiều đối tượng: Phản ánh nhanh hơn.