Track memory usage of a method
I have yet to find an elegant solution for this. I have a class with a method I want to track the memory usage of without modifying the function:
class Example
{
public function hello($name)
{
$something = str_repeat($name, pow(1024, 2));
}
}
$class = new Example;
$class->hello('a');
So the task is, how much memory does hello()
use without interferring with 开发者_JS百科it?
Note: The memory usage of this method should be 1MB. I've tried wrapping the call with memory_get_usage();
to no avail:
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;
This only results in 144
bytes (Not correct at all). I've tried various magic with Reflection by using the ReflectionMethod
class.
I have a feeling what I need to do is calculate the difference in the method :(. If someone can think of anything cleaner then you'd really make my day.
Edit: I should mention this is in the context of a benchmarking application. So while memory_get_peak_usage
works in the sense that it correctly returns the memory usage, it will also skew benchmarks ran after a high memory method. Now if there was a way to reset the memory stats, that could be good also.
You could use register_tick_function
and just dump memeory_get_usage
out every tick (line) and analysis it later. The class below could be improved by using debug_backtrace
to find line number related to memory usage or adding time per line using microtime
.
Profiler class
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;
}
}
Example
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 );
Output
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
)
)
Possible Issue
Since this profiler class stores the data in PHP, the overall memory usage will increase artificially. One way to sidestep this issue would be to write the data out to a file as you go (serialized), and when your done you can read it back.
The XHProfLive profiler developed by the Facebook guys gives this degree of function/method-level profiling, and available as a PECL download.
The memory is released when you are returning from the function.
You might add the $s = memory_get_usage();
...
echo memory_get_usage() - $s;
block inside of the function. This way, the memory used will not be released.
It looks like it has already 'freed up' memory after the call to hello() has ended.
What are the results when you do:
$s = memory_get_usage();
$class->hello('a');
echo memory_get_peak_usage() - $s;
You should use a php memory tool.
There is a nice one to be found in this SO thread: Tools to visually analyze memory usage of a PHP app
this other question holds some further answers regarding your question
The only trustable method I know to achieve this is profiling with tools that are not written in php itself.
Read this:
http://www.xdebug.org/docs/profiler
I have faced this issue recently to test that some functions are memory-efficient in Unit tests.
It requires just the Xdebug extension, as it uses Xdebug Profiler under the hood, generating and parsing the same trace files that Kachegrind would. It uses Xdebug functions to programmatically start a Profile on-demand against the function under test, then it parses the trace file, finds the peak memory execution inside that function and return it.
/**
* 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;
}
Example results:
$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
)
Optionally, you can return the memory used and the result, to assert on both, by changing just two lines:
// 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 ];
This way you can assert the memory usage is expected, while giving the expected result.
If you are going to use this in tests, you can also add this helpful method to the memory trait:
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 ) );
}
Example usage:
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
}
Why not register_ticks_function
?
After I wrote this answer, I also tested the register_ticks_function
mentioned by another answer to this question, however, I found out it requires declare(ticks=1)
on the file where the function under test is, and all files where the function under test uses code from, otherwise the tick won't be triggered. The Xdebug approach works without the declare, so it works with all files and tracks memory usage nested deep into the function under test calls.
精彩评论