【问题标题】:Track memory usage of a method跟踪方法的内存使用情况
【发布时间】:2011-05-16 05:33:12
【问题描述】:

我还没有找到一个优雅的解决方案。我有一个类,我想在不修改函数的情况下跟踪内存使用情况:

class Example
{
    public function hello($name)
    {
        $something = str_repeat($name, pow(1024, 2));
    }
}

$class = new Example;
$class->hello('a');

那么任务是,hello() 在不干扰的情况下使用了多少内存?

注意:此方法的内存使用量应为1MB。我尝试用memory_get_usage(); 结束通话,但无济于事:

class Example
{
    public function hello($name)
    {
        $something = str_repeat($name, pow(1024, 2));
    }
}

$class = new Example;

$s = memory_get_usage();

$class->hello('a');

echo memory_get_usage() - $s;

这只会产生144 字节(根本不正确)。我通过使用 ReflectionMethod 类尝试了反射的各种魔法。

我有一种感觉,我需要做的是计算方法中的差异:(。如果有人能想到任何更清洁的东西,那么你真的会让我开心。

编辑:我应该提到这是在基准测试应用程序的上下文中。因此,虽然memory_get_peak_usage 在正确返回内存使用情况的意义上有效,但它也会扭曲在高内存方法之后运行的基准测试。现在,如果有办法重置内存统计信息,那也不错。

【问题讨论】:

标签: php function memory methods


【解决方案1】:

您可以使用register_tick_function 并将memeory_get_usage 转储出每个刻度(行)并稍后进行分析。可以通过使用debug_backtrace 查找与内存使用相关的行号或使用microtime 添加每行时间来改进下面的类。

分析器类

class Profiler
{

    private $_data_array = array();

    function __construct()
    {
        register_tick_function( array( $this, "tick" ) );
        declare(ticks = 1);
    }

    function __destruct()
    {
        unregister_tick_function( array( $this, "tick" ) );
    }

    function tick()
    {
        $this->_data_array[] = array(
            "memory" => memory_get_usage(),
            "time" => microtime( TRUE ),
            //if you need a backtrace you can uncomment this next line
            //"backtrace" => debug_backtrace( FALSE ),
        );
    }

    function getDataArray()
    {
        return $this->_data_array;
    }
}

示例

class Example
{
    public function hello($name)
    {
        $something = str_repeat($name, pow(1024, 2));
    }
}

$profiler = new Profiler(); //starts logging when created

$class = new Example;
$class->hello('a');

$data_array = $profiler->getDataArray();

unset( $profiler ); //stops logging when __destruct is called

print_r( $data_array );

输出

Array (
    [0] => Array (
            [memory] => 638088
            [time] => 1290788749.72
        )
    [1] => Array (
            [memory] => 638896
            [time] => 1290788749.72
        )
    [2] => Array (
            [memory] => 639536
            [time] => 1290788749.72
        )
    [3] => Array (
            [memory] => 640480
            [time] => 1290788749.72
        )
    [4] => Array (
            [memory] => 1689800 // <~ money!
            [time] => 1290788749.72
        )
    [5] => Array (
            [memory] => 641664
            [time] => 1290788749.72
        )
)

可能的问题

由于此分析器类将数据存储在 PHP 中,因此整体内存使用量会人为地增加。回避此问题的一种方法是在执行过程中将数据写入文件(序列化),完成后您可以将其读回。

【讨论】:

  • 稍加工作,这很好。感谢您的创造性解决方案,不要认为我们会找到更好的答案 :-) 接受。
【解决方案2】:

Facebook 开发人员开发的XHProfLive 分析器提供了这种程度的函数/方法级分析,并以PECL download 的形式提供。

【讨论】:

    【解决方案3】:

    从函数返回时释放内存。

    您可以添加$s = memory_get_usage(); ... echo memory_get_usage() - $s; 在函数内部阻塞。这样使用的内存就不会被释放。

    【讨论】:

      【解决方案4】:

      在对 hello() 的调用结束后,它看起来已经“释放”了内存。

      你这样做的结果是什么:

      $s = memory_get_usage();
      
      $class->hello('a');
      
      echo memory_get_peak_usage() - $s;
      

      【讨论】:

      • 我已经添加了关于 memory_get_peak_usage() 的评论,谢谢。
      【解决方案5】:

      你应该使用php记忆工具。

      在这个 SO 线程中有一个很好的发现: Tools to visually analyze memory usage of a PHP app

      this other question 包含有关您的问题的进一步答案

      【讨论】:

        【解决方案6】:

        我知道实现这一点的唯一可靠方法是使用不是用 php 本身编写的工具进行分析。

        阅读:

        http://www.xdebug.org/docs/profiler

        【讨论】:

          【解决方案7】:

          我最近遇到了这个问题,以测试单元测试中的某些函数是否具有内存效率。

          它只需要 Xdebug 扩展,因为它在底层使用 Xdebug Profiler,生成和解析与 Kachegrind 相同的跟踪文件。它使用 Xdebug 函数以编程方式根据被测函数按需启动 Profile,然后解析跟踪文件,找到该函数内部的峰值内存执行并将其返回。

          /**
           * Returns the peak memory usage in bytes during the execution of a given function.
           *
           * @param callable $callback A callable to the function to check the memory usage.
           * @param mixed ...$parameters The arguments to the callable function.
           *
           * @throws RuntimeException When Xdebug is not available, or
           *
           * @return int The peak memory usage in bytes that this function consumed during execution.
           */
          protected function get_function_memory_usage( callable $callback, ...$parameters ) {
              if ( ! function_exists( 'xdebug_stop_trace' ) ) {
                  throw new RuntimeException('Xdebug is required for this test.');
              }
          
              $trace_file = xdebug_start_trace( null, XDEBUG_TRACE_COMPUTERIZED );
              call_user_func_array( $callback, $parameters );
              xdebug_stop_trace();
          
              $trace_file = new SplFileObject( $trace_file );
          
              $start_context_memory_usage = 0;
              $highest_memory_usage       = 0;
          
              /*
               * A small Xdebug Tracefile analyser that looks for the highest memory allocation
               * during the execution of the function
               *
               * @link https://github.com/xdebug/xdebug/blob/master/contrib/tracefile-analyser.php
               */
              while ( $trace_file->valid() ) {
                  $line = $trace_file->fgets();
          
                  if (
                      preg_match( '@^Version: (.*)@', $line, $matches ) ||
                      preg_match( '@^File format: (.*)@', $line, $matches ) ||
                      preg_match( '@^TRACE.*@', $line, $matches )
                  ) {
                      continue;
                  }
          
                  $trace_entry = explode( "\t", $line );
          
                  if ( count( $trace_entry ) < 5 ) {
                      continue;
                  }
          
                  $memory = (int) $trace_entry[4];
          
                  if ( $memory > $highest_memory_usage ) {
                      $highest_memory_usage = $memory;
                  }
          
                  if ( empty( $start_context_memory_usage ) ) {
                      $start_context_memory_usage = $memory;
                  }
              }
          
              $memory_used = $highest_memory_usage - $start_context_memory_usage;
          
              return $memory_used;
          }
          

          示例结果:

          $function_uses_memory = static function( $mbs ) {
              $a = str_repeat('a', 1024 * 1024 * $mbs);
          };
          
          $memory_usage_5 = get_function_memory_usage( $function_uses_memory, 5 );
          $memory_usage_40 = get_function_memory_usage( $function_uses_memory, 40 );
          $memory_usage_62 = get_function_memory_usage( $function_uses_memory, 62 );
          $memory_usage_30 = get_function_memory_usage( $function_uses_memory, 30 );
          
          // Result:
          (
              [$memory_usage_5]  => 5247000
              [$memory_usage_40] => 41947160
              [$memory_usage_62] => 65015832
              [$memory_usage_30] => 31461400
          )
          

          或者,您可以通过仅更改两行来返回使用的内存和结果,以断言两者:

          // Store the result after calling the callback
          $result = call_user_func_array( $callback, $parameters );
          
          // Return the result along with the memory usage
          return [ $result, $memory_used ];
          

          通过这种方式,您可以断言预期的内存使用量,同时给出预期的结果。

          如果你打算在测试中使用它,你也可以将这个有用的方法添加到内存特征中:

          protected function assertMemoryUsedWithinTolerance( $expected, $equal, $tolerance_percentage ) {
              $is_within_tolerance = static function ( $expected, $actual ) use ( $tolerance_percentage ) {
                  $upper_limit = $expected + ( ( $expected / 100 ) * $tolerance_percentage );
          
                  return $actual <= $upper_limit;
              };
          
              $failure_message = static function ( $expected, $actual ) use ( $tolerance_percentage ) {
                  return sprintf( 'Failed to assert that a function uses up to %s bytes (%s) of memory (+%s%%). Actual usage: %s bytes (%s).', $expected, size_format( $expected, 4 ), $tolerance_percentage, $actual, size_format( $actual, 4 ) );
              };
          
              $this->assertTrue( $is_within_tolerance( $expected, $equal ), $failure_message( $expected, $equal ) );
          }
          

          示例用法:

          public function test_memory_usage_parsing_long_string() {
              $long_input = str_repeat('a', 1024 * 1024); // 1mb string
          
              [ $result, $memory_used ] = $this->get_function_memory_usage( [$this, 'parse_long_string'], $long_input );
          
              // If it exceeds 2x the size of the input it should fail. 
              $expected_memory_peak = 1024 * 1024 * 2; // 2mb
          
              $this->assertEquals('foo', $result);
              $this->assertMemoryUsedWithinTolerance($expected_memory_peak, $memory_used, 1); // 1% tolerance
          }
          
          

          为什么不register_ticks_function

          在我写完这个答案之后,我还测试了这个问题的另一个答案提到的register_ticks_function,但是,我发现它需要declare(ticks=1) 在被测函数所在的文件以及函数所在的所有文件上under test 使用来自的代码,否则不会触发滴答声。 Xdebug 方法在没有声明的情况下工作,因此它适用于所有文件并跟踪嵌套在被测函数调用深处的内存使用情况。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-01-14
            • 1970-01-01
            • 2011-01-18
            • 1970-01-01
            • 2017-06-17
            • 2017-02-14
            • 1970-01-01
            • 2012-04-01
            相关资源
            最近更新 更多