Skip to content

Instantly share code, notes, and snippets.

@jcupitt
Last active April 4, 2018 06:02
Show Gist options
  • Save jcupitt/c54f874c33bea52ea6c3f270873978a9 to your computer and use it in GitHub Desktop.
Save jcupitt/c54f874c33bea52ea6c3f270873978a9 to your computer and use it in GitHub Desktop.
from __future__ import print_function
import timeit
setup = '''
import pyvips
# libvips is a demand-driven image processing library -- a line like
#
# x = y.similarity(angle=20)
#
# will not perform any computation, it just adds a new node to a pipeline.
# Computation only happens when you connect a pipeline to a sink (a thing that
# will demand pixels). When this happens, the entire pipeline executes all at
# once and in parallel. libvips is able to overlap JPG decode, rotation, and JPG
# encode. This can give a nice speed boost and drop in latency, especially on
# machines with many cores. It also drops memory use right down, since it never
# needs to have all the image present at once.
# This benchmark is just timing a single rotate operation from a memory source
# to a memory destination. To set this up, we must run two pipelines: one from
# the JPG source into a memory buffer (which we don't time), and a second
# pipeline (which we do time) running from that first memory buffer into a
# second.
# A test like this misses two useful libvips features: it does not show off the
# ability to overlap operations (which larger programs should benefit
# from), and it does not show the memory savings you get with streaming (very
# useful for large images).
# The speed and memory use benchmark on the libvips wiki has timings for a
# slightly larger test program:
#
# https://github.com/jcupitt/libvips/wiki/Speed-and-memory-use
# a 1450 x 2048 RGB jpg
test_file = "/home/john/pics/k2.jpg"
# this will read the header from the file, but not decode any pixels ... pixels
# are only decoded on demand
im = pyvips.Image.new_from_file(test_file)
# this will connect im to a memory sink and pull pixels from the source into the
# memory buffer
memory = im.copy_memory()
# this will start a new pipeline working from the memory buffer ... but it will
# not do any computation! that will only happen when this second pipeline
# connects to a sink
im2 = memory.similarity(angle=20)
# libvips always outs all pixels on rotate, but PIL only outputs the centre area,
# the same size as the original -- to make it fair, we crop the libvips one
# down
# this extra crop operation actually makes libvips quicker, since it will have
# fewer pixels to compute
im3 = im2.crop((im2.width - memory.width) / 2,
(im2.height - memory.height) / 2,
memory.width,
memory.height)
'''
stmt = '''
# this will execute the second pipeline working from the memory buffer into
# a second memory buffer
memory2 = im3.copy_memory()
'''
print('vips', timeit.timeit(stmt=stmt, setup=setup, number=100) / 100)
setup = '''
from PIL import Image
test_file = "/home/john/pics/k2.jpg"
im = Image.open(test_file)
'''
stmt='''
im2 = im.rotate(20, resample=Image.BILINEAR)
'''
print('pillow', timeit.timeit(stmt=stmt, setup=setup, number=100) / 100)
@jcupitt
Copy link
Author

jcupitt commented Nov 18, 2017

On my two-core, four-thread laptop with pillow 4.3 and libvips 8.5:

john@kiwi:~/try$ python bench.py
vips 0.0544475102425
pillow 0.153400540352

With pillow-simd 4.3 I see:

john@kiwi:~/try$ python bench.py
vips 0.0544475102425
pillow 0.116245059967

@jcupitt
Copy link
Author

jcupitt commented Nov 19, 2017

libvips was outputting a larger image than PIL. I cropped the libvips one down to the same size and I now see:

john@kiwi:~/try$ python bench.py
vips 0.0445712780952
pillow 0.151649999619

so about 3.4x faster

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment