Huge memory usage, despite using SapiStreamEmitter
Huge memory usage, despite using SapiStreamEmitter
When using Zend\Diactoros\Server with ->setEmitter(new \Zend\Diactoros\Response\SapiStreamEmitter());, it is still huge memory usage.
here is reproduce code.
<?php
use Zend\Diactoros\Server;
// zendframework/zend-diactoros version is 1.3.10
require_once __DIR__.'/vendor/autoload.php';
$server = Server::createServer(function () {
// create empty 10MB file
// $ dd if=/dev/zero of=tempfile bs=1M count=10
return new \Zend\Diactoros\Response(fopen('tempfile', 'r'));
}, $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
$server->setEmitter(new \Zend\Diactoros\Response\SapiStreamEmitter());
$server->listen();
// for builtin server
file_put_contents("php://stdout", "\nMemory Usage: " . formatBytes(memory_get_peak_usage(true)));
function formatBytes($bytes, $precision = 2) {
if ( abs($bytes) < 1024 ) $precision = 0;
$sign = '';
if ( $bytes < 0 ) {
$sign = '-';
$bytes = abs($bytes);
}
$exp = floor(log($bytes) / log(1024));
$bytes = sprintf('%.'.$precision.'f', ($bytes / pow(1024, floor($exp))));
return $sign . $bytes .' '. ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$exp];
}
expected Memory Usage is 2.00 MB,
but showned Memory Usage is 16.00 MB.
I think that reason is Server::listen() calls ob_start,
but did not clean output buffer (ob_end_clean)
First, for anyone reading this, you need to apply PR #233 before running this code.
@sasezaki your suggestion will not work. However, may be interesting to add an "ob_end_flush" at the end of "listen" method (not ob_end_clean).
In order to reduce memory usage is necessary a call to "ob_flush" after each "read" call in SapiStreamEmitter.php.
However I believe this may cause performance issues in most production environments.
The flush calls has a very high performance cost.
And besides this, the PHP engine already regularly flushes output buffer, following "output_buffering" configuration directive.
Which I believe is more appropriate because it allows the application admin to choose the best balance between resource consumption and performance.
And depending of layers beyond PHP engine, the "ob_flush" may be pointless (e.g. Apache buffering when using gzip).
It has been months (literally) since I started looking for a solution.
In my case, here is what did the trick - Note that I'm only using the SapiStreamEmitter:
ob_end_flush();
ob_implicit_flush();
$emitter->emit($response);
I'm not sure if this is really efficient, but at least it works (with large files ofc). I still need to try it in prod though..
Not taking any credit here. In fact, this is from an answer (that have some up votes) on ob_implicit_flush() function at php.net: https://secure.php.net/manual/fr/function.ob-implicit-flush.php#116748
EDIT - After some more investigation.. We can simply do this. Way more cleaner.
ob_implicit_flush();
$maxBufferLevel = 0; // Stands for the maximum (and last) output buffering level to unwrap
$emitter->emit($response, $maxBufferLevel);
This repository has been closed and moved to laminas/laminas-diactoros; a new issue has been opened at https://github.com/laminas/laminas-diactoros/issues/14.