-
Notifications
You must be signed in to change notification settings - Fork 60
Add data_ptr
method to Vips::Image
#418
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
base: master
Are you sure you want to change the base?
Conversation
Hi @ankane, thanks for this! Unfortunately, https://www.libvips.org/API/current/libvips-header.html#vips-image-get-data The threadsafe equivalent is https://libvips.github.io/ruby-vips/Vips/Image.html#copy_memory-instance_method https://www.libvips.org/API/current/VipsImage.html#vips-image-copy-memory |
Thanks @jcupitt. It sounds like the thread-safe way to get a read-only pointer with minimal copying is to combine the two. copy = image.copy_memory
read_ptr = copy.data_ptr What do you think about having a |
Yes, that's better. Having What happens if the caller slices the result from |
Sounds good. Calling |
Great! Please add a note to the changelog (and credit yourself!), and a note to the docs saying this is potentially a very expensive operation. Thank you for doing this work! |
Thanks @jcupitt. Re expensive: I feel like I'm missing something about how libvips works that may make this PR not very useful. Both image = Vips::Image.new_from_file("image.png")
ptr = image.read_ptr
p image.write_to_memory == ptr.read_string(ptr.size) # => true However, require "benchmark/ips"
require "ruby-vips"
Benchmark.ips do |x|
x.report("write_to_memory") do
image = Vips::Image.new_from_file("image.png")
image.write_to_memory
end
x.report("read_ptr") do
image = Vips::Image.new_from_file("image.png")
image.read_ptr
end
end Output
I'd expect them to be roughly the same when the image pixels aren't already loaded (since they produce the same result), and I'd expect |
You're right, I'd expect |
I made a small C prog for testing: // compile with
// gcc -g -Wall copywrite.c `pkg-config vips --cflags --libs`
#include <vips/vips.h>
int
main(int argc, char **argv)
{
VIPS_INIT(argv[0]);
// 2.33s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
size_t size = VIPS_IMAGE_SIZEOF_IMAGE(image);
void *buf = g_try_malloc(size);
VipsImage *x = vips_image_new_from_memory(buf, size,
image->Xsize, image->Ysize, image->Bands, image->BandFmt);
vips_image_write(image, x);
g_object_unref(x);
g_free(buf);
g_object_unref(image);
}
// 2.93s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
VipsImage *x = vips_image_new_memory();
vips_image_write(image, x);
g_object_unref(x);
g_object_unref(image);
}
// 2.33s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
void *buf = vips_image_write_to_memory(image, NULL);
g_free(buf);
g_object_unref(image);
}
return 0;
} The middle one is what Anyway, the difference is relatively small, and not the huge difference you are seeing, which means it must be an inefficiency in ruby-vips. The same program in ruby-vips is: #!/usr/bin/env ruby
require "ruby-vips"
1000.times do |i|
puts "loop #{i} ..."
image = Vips::Image.new_from_file(ARGV[0])
# 4.0s, 200mb
#image.write_to_memory
# 18.9s, 2.1gb
image.copy_memory
end So Looking at the code, it's just: def copy_memory
new_image = Vips.vips_image_copy_memory self
Vips::Image.new new_image
end Which (I think?) should be OK. I'll keep digging. |
Changing def copy_memory
new_image = Vips.vips_image_copy_memory self
::GObject.g_object_unref new_image
end Obviously won't work, haha, but it gets the time down to 3.2s. If I make it: def copy_memory
Vips::Image.new_from_memory self.write_to_memory,
self.width, self.height, self.bands, self.format
end I see 4.0s, 240mb, so the same as I'm not sure why |
Sampling the following program on Mac arm64: require "ruby-vips"
loop do
image = Vips::Image.new_from_file("image.png")
image.copy_memory
end shows it's spending most of the time (~95%) on
|
That's very strange. I'll try sysprof here. |
Hi, this PR adds support for vips_image_get_data to Ruby with a new
data_ptr
method that returns a sizedFFI::Pointer
. This provides similar functionality aswrite_to_memory
but without additional copying.With the current implementation, it would be the user's responsibility to ensure the pointer has not been invalidated (but we could add a reference to the pointer to ensure the image is not garbage collected while the pointer is active - I'm not sure if there are other operations that invalidate the pointer).
Ref: FFI enum docs