mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Use Persistent wrapper to prevent GC of input Buffer #152
Avoids memcpy of input and output Buffer objects Improves Buffer and Stream performance by ~3%
This commit is contained in:
parent
20f468991f
commit
ede2ee9ce3
@ -47,7 +47,13 @@ libvips must be installed before `npm install` is run.
|
|||||||
This can be achieved via homebrew:
|
This can be achieved via homebrew:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
brew install homebrew/science/vips
|
||||||
|
```
|
||||||
|
|
||||||
|
For GIF input and WebP output suppport use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install homebrew/science/vips --with-graphicsmagick --with-webp
|
||||||
```
|
```
|
||||||
|
|
||||||
A missing or incorrectly configured _Xcode Command Line Tools_ installation
|
A missing or incorrectly configured _Xcode Command Line Tools_ installation
|
||||||
|
@ -64,20 +64,15 @@ struct MetadataBaton {
|
|||||||
iccLength(0) {}
|
iccLength(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Delete input char[] buffer and notify V8 of memory deallocation
|
|
||||||
Used as the callback function for the "postclose" signal
|
|
||||||
*/
|
|
||||||
static void DeleteBuffer(VipsObject *object, char *buffer) {
|
|
||||||
if (buffer != NULL) {
|
|
||||||
delete[] buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataWorker : public AsyncWorker {
|
class MetadataWorker : public AsyncWorker {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MetadataWorker(Callback *callback, MetadataBaton *baton) : AsyncWorker(callback), baton(baton) {}
|
MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
|
||||||
|
AsyncWorker(callback), baton(baton) {
|
||||||
|
if (baton->bufferInLength > 0) {
|
||||||
|
SaveToPersistent("bufferIn", bufferIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
~MetadataWorker() {}
|
~MetadataWorker() {}
|
||||||
|
|
||||||
void Execute() {
|
void Execute() {
|
||||||
@ -91,17 +86,12 @@ class MetadataWorker : public AsyncWorker {
|
|||||||
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
|
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
|
||||||
if (image != NULL) {
|
if (image == NULL) {
|
||||||
// Listen for "postclose" signal to delete input buffer
|
|
||||||
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Input buffer has corrupt header");
|
(baton->err).append("Input buffer has corrupt header");
|
||||||
imageType = ImageType::UNKNOWN;
|
imageType = ImageType::UNKNOWN;
|
||||||
DeleteBuffer(NULL, baton->bufferIn);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
(baton->err).append("Input buffer contains unsupported image format");
|
||||||
DeleteBuffer(NULL, baton->bufferIn);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// From file
|
// From file
|
||||||
@ -192,6 +182,11 @@ class MetadataWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
argv[1] = info;
|
argv[1] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
|
||||||
|
if (baton->bufferInLength > 0) {
|
||||||
|
GetFromPersistent("bufferIn");
|
||||||
|
}
|
||||||
delete baton;
|
delete baton;
|
||||||
|
|
||||||
// Return to JavaScript
|
// Return to JavaScript
|
||||||
@ -215,17 +210,16 @@ NAN_METHOD(metadata) {
|
|||||||
// Input filename
|
// Input filename
|
||||||
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
|
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
|
||||||
// Input Buffer object
|
// Input Buffer object
|
||||||
|
Local<Object> bufferIn;
|
||||||
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
||||||
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
||||||
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
|
baton->bufferInLength = node::Buffer::Length(bufferIn);
|
||||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
baton->bufferIn = node::Buffer::Data(bufferIn);
|
||||||
baton->bufferIn = new char[baton->bufferInLength];
|
|
||||||
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join queue for worker thread
|
// Join queue for worker thread
|
||||||
Callback *callback = new Callback(info[1].As<Function>());
|
Callback *callback = new Callback(info[1].As<Function>());
|
||||||
AsyncQueueWorker(new MetadataWorker(callback, baton));
|
AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
|
||||||
|
|
||||||
// Increment queued task counter
|
// Increment queued task counter
|
||||||
g_atomic_int_inc(&counterQueue);
|
g_atomic_int_inc(&counterQueue);
|
||||||
|
@ -32,7 +32,7 @@ using Nan::Get;
|
|||||||
using Nan::Set;
|
using Nan::Set;
|
||||||
using Nan::To;
|
using Nan::To;
|
||||||
using Nan::New;
|
using Nan::New;
|
||||||
using Nan::CopyBuffer;
|
using Nan::NewBuffer;
|
||||||
using Nan::Null;
|
using Nan::Null;
|
||||||
using Nan::Equals;
|
using Nan::Equals;
|
||||||
|
|
||||||
@ -167,21 +167,15 @@ struct PipelineBaton {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Delete input char[] buffer and notify V8 of memory deallocation
|
|
||||||
Used as the callback function for the "postclose" signal
|
|
||||||
*/
|
|
||||||
static void DeleteBuffer(VipsObject *object, char *buffer) {
|
|
||||||
if (buffer != NULL) {
|
|
||||||
delete[] buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PipelineWorker : public AsyncWorker {
|
class PipelineWorker : public AsyncWorker {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener) :
|
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener, const Local<Object> &bufferIn) :
|
||||||
AsyncWorker(callback), baton(baton), queueListener(queueListener) {}
|
AsyncWorker(callback), baton(baton), queueListener(queueListener) {
|
||||||
|
if (baton->bufferInLength > 0) {
|
||||||
|
SaveToPersistent("bufferIn", bufferIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
~PipelineWorker() {}
|
~PipelineWorker() {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -208,18 +202,13 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||||
if (inputImageType != ImageType::UNKNOWN) {
|
if (inputImageType != ImageType::UNKNOWN) {
|
||||||
image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod);
|
image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod);
|
||||||
if (image != NULL) {
|
if (image == NULL) {
|
||||||
// Listen for "postclose" signal to delete input buffer
|
|
||||||
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
|
|
||||||
} else {
|
|
||||||
// Could not read header data
|
// Could not read header data
|
||||||
(baton->err).append("Input buffer has corrupt header");
|
(baton->err).append("Input buffer has corrupt header");
|
||||||
inputImageType = ImageType::UNKNOWN;
|
inputImageType = ImageType::UNKNOWN;
|
||||||
DeleteBuffer(NULL, baton->bufferIn);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
(baton->err).append("Input buffer contains unsupported image format");
|
||||||
DeleteBuffer(NULL, baton->bufferIn);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// From file
|
// From file
|
||||||
@ -969,10 +958,8 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
|
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
|
||||||
|
|
||||||
if (baton->bufferOutLength > 0) {
|
if (baton->bufferOutLength > 0) {
|
||||||
// Copy data to new Buffer
|
// Pass ownership of output data to Buffer instance
|
||||||
argv[1] = CopyBuffer(static_cast<char*>(baton->bufferOut), baton->bufferOutLength).ToLocalChecked();
|
argv[1] = NewBuffer(static_cast<char*>(baton->bufferOut), baton->bufferOutLength).ToLocalChecked();
|
||||||
// bufferOut was allocated via g_malloc
|
|
||||||
g_free(baton->bufferOut);
|
|
||||||
// Add buffer size to info
|
// Add buffer size to info
|
||||||
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
||||||
argv[2] = info;
|
argv[2] = info;
|
||||||
@ -984,8 +971,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
argv[1] = info;
|
argv[1] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
|
||||||
|
if (baton->bufferInLength > 0) {
|
||||||
|
GetFromPersistent("bufferIn");
|
||||||
|
}
|
||||||
delete baton;
|
delete baton;
|
||||||
// to here
|
|
||||||
|
|
||||||
// Decrement processing task counter
|
// Decrement processing task counter
|
||||||
g_atomic_int_dec_and_test(&counterProcess);
|
g_atomic_int_dec_and_test(&counterProcess);
|
||||||
@ -1132,12 +1123,11 @@ NAN_METHOD(pipeline) {
|
|||||||
To<bool>(Get(options, New("sequentialRead").ToLocalChecked()).ToLocalChecked()).FromJust() ?
|
To<bool>(Get(options, New("sequentialRead").ToLocalChecked()).ToLocalChecked()).FromJust() ?
|
||||||
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||||
// Input Buffer object
|
// Input Buffer object
|
||||||
|
Local<Object> bufferIn;
|
||||||
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
|
||||||
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
|
||||||
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
|
baton->bufferInLength = node::Buffer::Length(bufferIn);
|
||||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
baton->bufferIn = node::Buffer::Data(bufferIn);
|
||||||
baton->bufferIn = new char[baton->bufferInLength];
|
|
||||||
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
|
|
||||||
}
|
}
|
||||||
// ICC profile to use when input CMYK image has no embedded profile
|
// ICC profile to use when input CMYK image has no embedded profile
|
||||||
baton->iccProfilePath = *Utf8String(Get(options, New("iccProfilePath").ToLocalChecked()).ToLocalChecked());
|
baton->iccProfilePath = *Utf8String(Get(options, New("iccProfilePath").ToLocalChecked()).ToLocalChecked());
|
||||||
@ -1212,11 +1202,10 @@ NAN_METHOD(pipeline) {
|
|||||||
|
|
||||||
// Join queue for worker thread
|
// Join queue for worker thread
|
||||||
Callback *callback = new Callback(info[1].As<Function>());
|
Callback *callback = new Callback(info[1].As<Function>());
|
||||||
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener));
|
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn));
|
||||||
|
|
||||||
// Increment queued task counter
|
// Increment queued task counter
|
||||||
g_atomic_int_inc(&counterQueue);
|
g_atomic_int_inc(&counterQueue);
|
||||||
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
||||||
queueListener->Call(1, queueLength);
|
queueListener->Call(1, queueLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
"test": "node perf && node random && node parallel"
|
"test": "node perf && node random && node parallel"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.4.2",
|
"async": "^1.5.0",
|
||||||
"benchmark": "^1.0.0",
|
"benchmark": "^1.0.0",
|
||||||
"gm": "^1.20.0",
|
"gm": "^1.21.0",
|
||||||
"imagemagick": "^0.1.3",
|
"imagemagick": "^0.1.3",
|
||||||
"imagemagick-native": "^1.8.0",
|
"imagemagick-native": "^1.8.0",
|
||||||
"jimp": "^0.2.8",
|
"jimp": "^0.2.19",
|
||||||
"lwip": "^0.0.8",
|
"lwip": "^0.0.8",
|
||||||
"semver": "^5.0.3"
|
"semver": "^5.0.3"
|
||||||
},
|
},
|
||||||
|
@ -278,11 +278,18 @@
|
|||||||
...
|
...
|
||||||
fun:vips__magick_read_header
|
fun:vips__magick_read_header
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
cond_magick_is_palette_image
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:IsPaletteImage
|
||||||
|
...
|
||||||
|
fun:get_bands
|
||||||
|
}
|
||||||
|
|
||||||
# glib g_file_read_link
|
# glib g_file_read_link
|
||||||
# https://github.com/GNOME/glib/commit/49a5d0f6f2aed99cd78f25655f137f4448e47d92
|
# https://github.com/GNOME/glib/commit/49a5d0f6f2aed99cd78f25655f137f4448e47d92
|
||||||
{
|
{
|
||||||
leak_magick
|
leak_g_file_read_link
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
match-leak-kinds: definite,indirect,possible
|
match-leak-kinds: definite,indirect,possible
|
||||||
...
|
...
|
||||||
|
Loading…
x
Reference in New Issue
Block a user