-
Notifications
You must be signed in to change notification settings - Fork 27
WIP -- work around php-ffi memory leaks #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Test program: #!/usr/bin/env php
<?php
require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;
function vips($filename) {
$image = Vips\Image::newFromFile($filename);
}
Vips\Config::cacheSetMax(0);
echo "iteration, growth (bytes)\n";
$prev = 0;
for ($i = 0; $i < 10000; $i++) {
vips($argv[1]);
if ($i % 100 == 0) {
gc_collect_cycles();
$now = memory_get_usage();
$use = $now - $prev;
$prev = $now;
echo "$i, $use\n";
}
} With this PR I now see:
So 10,000 iterations and no memory growth. With master I see:
A fairly steady 200 bytes per call. |
Any testing very welcome! |
If anyone is curious, the leak was caused by the line: return $pspec[0]; ie. dereferencing a managed array of pointers to unmanaged structs. php-ffi seems to make an unmanaged copy of the struct and the pointer in this case. This new code: https://github.com/libvips/php-vips/blob/revise-leaks/src/VipsObject.php#L109-L120 Does the dereference by hand (probably in a dumb way) and so fixes the leak. |
A rather complex long-running script is working fine now! No memory issues so far in any use case. |
This still leaks: #!/usr/bin/env php
<?php
require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;
#Vips\Config::setLogger(new Vips\DebugLogger());
Vips\Config::cacheSetMax(0);
if (count($argv) != 4) {
echo("usage: ./watermark-text.php input output \"some text\"\n");
exit(1);
}
$input_filename = $argv[1];
$output_filename = $argv[2];
$message = $argv[3];
function watermark($image, $message)
{
$text = Vips\Image::text($message, [
'width' => $image->width,
'dpi' => 600,
'rgba' => true
]);
// scale the alpha down to make it semi-transparent, and rotate by 45
// degrees
$text = $text
->multiply([1, 1, 1, 0.3])
->rotate(45);
// replicate and crop to match the size of the image
$text = $text
->replicate(1 + $image->width / $text->width,
1 + $image->height / $text->height)
->crop(0, 0, $image->width, $image->height);
// and overlay on the image
return $image->composite($text, "over");
}
echo "iteration, growth (bytes)\n";
$prev = 0;
for ($i = 0; $i < 2000; $i++) {
$image = Vips\Image::newFromFile($input_filename, [
'access' => 'sequential',
]);
// no leak
//$image = $image->invert()
// leaks 192 bytes per loop
$image = $image->add(1);
// leaks 288 bytes per loop
//$image = watermark($image, $message);
$image->writeToFile($output_filename);
if ($i % 10 == 0) {
gc_collect_cycles();
$now = memory_get_usage();
$use = $now - $prev;
$prev = $now;
echo "$i, $use\n";
}
} Looks like a problem with constant handling, I'll have a look. |
Hm... Looks like my complex code was accidentally not so complex (It has most of this operations, but depends on input and config). I'm sorry. |
Heh no problem, we nailed one leak, I'm sure there's just one more. |
php-ffi seems to leak if you use arrayType ... this commit switches to string arguments instead
I found another one -- php-ffi leaks 96 bytes every time you call #!/usr/bin/env php
<?php
require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;
// Vips\Config::setLogger(new Vips\DebugLogger());
// Vips\Config::cacheSetMax(0);
if (count($argv) != 4) {
echo("usage: ./watermark-text.php input output \"some text\"\n");
exit(1);
}
$input_filename = $argv[1];
$output_filename = $argv[2];
$message = $argv[3];
function watermark($image, $message)
{
$text = Vips\Image::text($message, [
'width' => min(300, $image->width),
'height' => min(300, $image->height),
'align' => 'centre',
'rgba' => true
]);
// scale the alpha down to make it semi-transparent, and rotate by 45
// degrees
$text = $text
->multiply([1, 1, 1, 0.3])
->rotate(45);
// replicate and crop to match the size of the image
$text = $text
->replicate(1 + $image->width / $text->width,
1 + $image->height / $text->height)
->crop(0, 0, $image->width, $image->height);
// and overlay on the image
return $image->composite($text, "over");
}
echo "iteration, growth (bytes)\n";
$prev = 0;
for ($i = 0; $i < 1000; $i++) {
$image = Vips\Image::newFromFile($input_filename, [
'access' => 'sequential',
]);
$image = watermark($image, $message);
$image->writeToFile($output_filename);
if ($i % 10 == 0) {
gc_collect_cycles();
$now = memory_get_usage();
$use = $now - $prev;
$prev = $now;
echo "$i, $use\n";
}
} I see:
Any more testing would be great! |
My naive search also led me to GValue/arrayType - so, a little proud of myself :) But while I tried to understand it a bit (FFI/c/memory things - are hard!) - i see you already found it and fix it, so cool! Testing it now, so far so good. |
Script with |
OK, let's merge and put out a new version with these fixes. Thanks! |
OK, tagged master as v2.1.1 |
Due to a bug (I think?) in php-ffi we leaked c. 100 bytes for every call to
getPspec()
. This could really add up in long running programs.Thanks @levmv!
See #167