Too much RAM is used
opened this issue · comments
\Amp\Loop::run(function () {
\Amp\Loop::repeat(5 * 1000, function () {
echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
});
$server = \Amp\Socket\listen("127.0.0.1:12001");
\Amp\call(function () use ($server) {
/**
* @var $client \Amp\Socket\ServerSocket
*/
while ($client = yield $server->accept()) {
\Amp\call(function () use ($client) {
echo "client connected" . PHP_EOL;
while (($chunk = yield $client->read()) !== null) {
echo "client data -> " . strlen($chunk) . PHP_EOL;
}
echo "client disonnected" . PHP_EOL;
});
}
});
});
What I am doing wrong here ?
If I connect 1000 users I get 100MB of RAM used
Some time ago I already have connected 30K and I have ~100MB of RAM
UPDATE
\Amp\Loop::run(function () {
\Amp\Loop::repeat(5 * 1000, function () {
echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
});
$clientHandler = asyncCoroutine(function (ServerSocket $client) {
echo "client connected" . PHP_EOL;
while (($chunk = yield $client->read()) !== null) {
echo "client data -> " . strlen($chunk) . PHP_EOL;
}
echo "client disonnected" . PHP_EOL;
});
$server = \Amp\Socket\listen("127.0.0.1:12001");
while ($client = yield $server->accept()) {
$clientHandler($client);
}
});
I try to use code from example, and I get the same problem
I think problem is here:
while (($chunk = yield $client->read()) !== null) {
echo "client data -> " . strlen($chunk) . PHP_EOL;
}
or I am doing something wrong ?
Could you also provide a script that provides the 1000 clients part? I'm currently very busy, but maybe I can look into it sooner then.
\Amp\Loop::run(function () {
echo "Driver -> " . get_class(\Amp\Loop::get()) . "\n";
\Amp\Loop::repeat(5 * 1000, function () {
echo "current RAM: " . round((memory_get_usage() / 1024 / 1024), 2) . "MB, pick RAM: " . round((memory_get_peak_usage() / 1024 / 1024), 2) . "MB\n";
});
for ($i = 0; $i < 1000; $i++) {
client($i);
}
});
/**
* @param $i
*
* @return \Amp\Promise<\Amp\Socket\ClientSocket>
*/
function client($i)
{
return \Amp\call(function () use ($i) {
/**
* @var $client \Amp\Socket\ClientSocket
*/
$client = yield \Amp\Socket\connect("127.0.0.1:12001");
\Amp\Loop::repeat(1000, function () use ($client, $i) {
$data = "test";
echo "write $i data -> " . strlen($data) . PHP_EOL;
$client->write($data);
});
\Amp\call(function () use ($i, $client) {
echo "client connected -> $i" . PHP_EOL;
while (($chunk = yield $client->read()) !== null) {
echo "read $i data -> " . strlen($chunk) . PHP_EOL;
}
echo "client disonnected -> $i" . PHP_EOL;
});
return $client;
});
}
@kelunik Sorry for delay, weekend :)
Observations: If no message is written, then no problems with RAM
I have test this with 7.1.3 with UvDriver & EvDriver, i will update my php and try with last 7.1.9 and report here
Using EventDriver
I currently get current RAM: 73.71MB, pick RAM: 73.78MB
for 1000 clients.
Don't worry about any such short delay, I'm anyway busy. :-)
When accepting the connection, but not reading from it, I currently get an InvalidWatcherError
, see amphp/byte-stream#20.
UPDATE: Tested on 7.1.9 fresh install with UvDriver & EvDriver I get the same, ~75MB RAM on 1K connected users
How much ram do you think is acceptable?
Right now I have a running server with some old version of amp_v2 and ~30K connected users use ~300MB of RAM, I think not more then 10M for 1K
I am rewriting that old server using all new features from amphp
and so why I think this is a bug, old version (some commits ago) use much less RAM
A client really shouldn't use much more then 5-10 KB RAM. Needing 50+ KB for a client is definitely far too much.
Sorry @bwoebi I didn't mention! There is some other logic in my old implementation, and when measure RAM I don't really spend a lot of time, I just memory_get_usage()
with all that logic, but now with simple server, there are much more RAM consumption
@umbri What's the commit ID used in the old implementation?
I'm getting below 5MB when I keep references to the clients, but don't read anything.
@kelunik same, problem is definitely somewhere in read()
This is caused by the new default chunk size of 65k instead of 8k. Currently there's no way to change that why using Amp\Socket\listen()
unfortunately.
@trowski Should we just add another parameter to listen()
?
@bwoebi It needs to be freed here: https://lxr.room11.org/xref/php-src%40master/ext/standard/file.c#1813
It should probably only do that if the read size is less than half the allocated space or something like that.
@kelunik not sure. Given the Zend allocator, it will typically lead to an extra copy. Often enough the data will be small enough to fit into the bucket allocator (<= 3072 bytes), causing it to allocate a new chunk of memory and copy the whole data into that newly allocated memory chunk.
Not sure whether I can generally advocate copying the whole read string in that case. However we may realloc on a page (4 KB) boundary here.
Additionally, it should be simple enough to fix the reproduce case, just set $chunk = null;
at the end of the while loop.
@bwoebi It should probably just truncate the string as stream_get_contents
does, see https://lxr.room11.org/xref/php-src%40master/main/streams/streams.c#1453
We might just want to switch to stream_get_contents
.
You can drastically improve the RAM usage by explicitly setting the $chunk
to null
in the reading coroutine. We can also add a chunkSize
option to ServerListenContext
.
I'm closing this, as $chunk = null
solves it.