Speed and Memory Use

From VipsWiki

(Difference between revisions)
Jump to: navigation, search
Revision as of 12:41, 11 August 2010
John (Talk | contribs)
(2 x Opteron 254 workstation, 2.7 GHz)
← Previous diff
Revision as of 12:41, 11 August 2010
John (Talk | contribs)
(Notes)
Next diff →
Line 44: Line 44:
On a quiet system, and the quickest real time of three runs recorded. I didn't try to clear the disc cache so the disc speed should not be a factor. On a quiet system, and the quickest real time of three runs recorded. I didn't try to clear the disc cache so the disc speed should not be a factor.
- 
-I tested with VIPS SVN trunk but older versions of VIPS are pretty similar. 
nip2 looks slow because of the long start-up time. Once the VIPS pipeline has been built, it process at the same speed as the Python version. It would seem faster on larger images or more complex calculations. nip2 looks slow because of the long start-up time. Once the VIPS pipeline has been built, it process at the same speed as the Python version. It would seem faster on larger images or more complex calculations.

Revision as of 12:41, 11 August 2010

We've written scripts to load an image, crop, shrink, sharpen and save again in a number of image processing systems. It's a trivial test, but it does give some idea of the speed and memory behaviour of these programs (and it's also quite fun to compare the code).

See also our main Benchmarks page for a more complex benchmark and timings on a variety of machines.

Contents

Results

2 x Opteron 254 workstation, 2.7 GHz

Software Run time (secs real) Memory (peak RSS MB)
VIPS C++ 7.18 1.8 13
VIPS Python 7.18 1.8 15
VIPS command-line 7.18 2.7 15
NetPBM 10 3.6 70
GraphicsMagick 1.3.5 3.8 240
PIL 1.1.6 4.4 188
VIPS nip2 7.17 5.1 85
RMagick 2.13.1 (ImageMagick 6.5.7) 5.3 670
ImageMagick 6.5.7 5.9 480
FreeImage 3.10 (incomplete) 7.0 180
ImageScience 1.2.1 (incomplete) 9.5 260
Octave 3.0.1 64 (est.) 8500 (est.)

Notes

All timings are for a 5,000 by 5,000 pixel 8-bit RGB image in uncompressed tiled TIFF format, 128 by 128 pixel tiles. Each test was run with something like:

time ./vips.sh wtc_tiled_small.tif wtc2.tif

On a quiet system, and the quickest real time of three runs recorded. I didn't try to clear the disc cache so the disc speed should not be a factor.

nip2 looks slow because of the long start-up time. Once the VIPS pipeline has been built, it process at the same speed as the Python version. It would seem faster on larger images or more complex calculations.

Octave aims to be a very high-level prototyping language and is not primarily targeting speed. I timed a 2,000 by 2,000 pixel monochrome JPEG and extrapolated from that.

Both ImageMagick and GraphicsMagick were compiled with Q16, ie. 16 bits per pixel.

FreeImage does not have a sharpening or convolution operation so I skipped that part of the benchmark.

NetPBM will not read tiled tiff so we used a striped tiff file for this one.

Implementations

Here it is in a variety of image processing systems.

VIPS Python

#!/usr/bin/python

import sys
from vipsCC import *

im = VImage.VImage (sys.argv[1])
im = im.extract_area (100, 100, im.Xsize () - 200, im.Ysize () - 200)
im = im.affine (0.9, 0, 0, 0.9, 0, 0, 0, 0,
        int (im.Xsize() * 0.9), int (im.Ysize() * 0.9))
mask = VMask.VIMask (3, 3, 8, 0, 
		  [-1, -1, -1, 
		   -1,  16, -1, 
		   -1, -1, -1])
im = im.conv (mask)
im.write (sys.argv[2])

VIPS nip2

#!/home/john/vips/bin/nip2 -s

main
  = error "usage: infile -o outfile", argc != 2
  = (sharpen @ shrink @ crop) (Image_file argv?1)
{
  crop x = extract_area 100 100 (x.width - 200) (x.height - 200) x;
  shrink = resize Interpolate_bilinear 0.9 0.9;
  sharpen = conv (Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]]);
}

VIPS command-line

#!/bin/bash

width=`header -f Xsize $1`
height=`header -f Ysize $1`

width=$((width - 200))
height=$((height - 200))

set -x

vips im_extract_area $1 t1.v 100 100 $width $height
vips im_affinei_all t1.v t2.v bilinear 0.9 0 0 0.9 0 0
cat > mask.con <<EOF
3 3 8 0
-1 -1 -1
-1 16 -1
-1 -1 -1
EOF
vips im_conv t2.v $2 mask.con
rm t1.v t2.v mask.con

VIPS C++

#include <vips/vips>

int main (int argc, char **argv)
{
        vips::VImage in (argv[1]);
        vips::VIMask mask (3, 3, 8, 0,
                -1, -1, -1, -1, 16,-1, -1, -1, -1);

        in.
                extract_area (100, 100, in.Xsize () - 200, in.Ysize () - 200).
                affine (0.9, 0, 0, 0.9, 0, 0,
                        0, 0, in.Xsize () * 0.9, in.Ysize () * 0.9).
                conv (mask).
                write (argv[2]);

        return 0;
}

PIL

#!/usr/bin/python 

import Image, sys
import ImageFilter 

im = Image.open (sys.argv[1])
im = im.crop ((100, 100, im.size[0] - 100, im.size[1] - 100))
im = im.resize ((int (im.size[0] * 0.9), int (im.size[1] * 0.9)),
        Image.BILINEAR) 
filter = ImageFilter.Kernel ((3, 3),
              (-1, -1, -1,
               -1, 16, -1,
               -1, -1, -1))
im = im.filter (filter)
im.save (sys.argv[2])

Octave

#!/usr/bin/octave -qf

pkg load image

im = imread(argv(){1});
im = im(101:end-100, 101:end-100);        % Crop
im = imresize(im, 0.9, 'linear');         % Shrink    
myFilter = [-1 -1 -1
        -1 16 -1
        -1 -1 -1]; 
im = conv2(double(im), myFilter);         % Sharpen
im = max(0, im ./ (max(max(im)) / 255));  % Renormalize
imwrite(argv(){2}, uint8(im));           % Write back again

ImageMagick

#!/bin/bash

# we crop on load, it's a bit quicker and saves some memory
# we can't crop 100 pixels with the crop-on-load syntax, so we have to
# find the width and height ourselves
width=`header -f Xsize $1`
height=`header -f Ysize $1`

width=$((width - 200))
height=$((height - 200))

set -x

convert "$1[${width}x${height}+100+100]" \
        -resize 90x90% \
        -convolve "-1, -1, -1, -1, 16, -1, -1, -1, -1" \
        $2

GraphicsMagick

GraphicsMagick does not have crop-on-load so we use -shave instead.

#!/bin/bash

set -x

gm convert $1 \
        -shave 100x100 \
        -resize 90x90% \
        -convolve "-1, -1, -1, -1, 16, -1, -1, -1, -1" \
        $2

FreeImage

/* Compile with:

   gcc freeimage.c -lfreeimage

 */

#include <FreeImage.h>

int
main (int argc, char **argv)
{       
  FIBITMAP *t1;
  FIBITMAP *t2;
  int width;
  int height;

  FreeImage_Initialise (FALSE);

  t1 = FreeImage_Load (FIF_TIFF, argv[1], TIFF_DEFAULT);

  width = FreeImage_GetWidth (t1); 
  height = FreeImage_GetHeight (t1); 

  t2 = FreeImage_Copy (t1, 100, 100, width - 100, height - 100); 
  FreeImage_Unload (t1); 

  t1 = FreeImage_Rescale (t2, (width - 200) * 0.9, (height - 200) * 0.9,
                          FILTER_BILINEAR);
  FreeImage_Unload (t2); 

  /* FreeImage does not have a sharpen operation, so we skip that.
   */

  FreeImage_Save (FIF_TIFF, t1, argv[2], TIFF_DEFAULT);
  FreeImage_Unload (t1); 

  FreeImage_DeInitialise ();

  return 0;
}      

NetPBM

#!/bin/bash

cat > mask <<EOF
P2
3 3
32
14 14 14 
14 48 14
14 14 14
EOF

tifftopnm $1 | \
  pnmcut -left 100 -right -100 -top 100 -bottom -100 | \
  pnmscale 0.9 | \
  pnmconvol mask | \
  pnmtotiff -truecolor -color > $2

ImageScience

ImageScience is a popular Ruby extension for making thumbnails. It's based on FreeImage.

It does not support sharpening, so I've skipped that part of the test. The resize() method is always bicubic which is a little unfair as the other benchmarks here use bilinear. It also generates sections of the wrapper at run-time, which again hurts it's position in the table at the top.

#!/usr/bin/ruby

require 'rubygems'
require 'image_science'

ImageScience.with_image(ARGV[0]) do |img|
    img.with_crop(100, 100, img.width() - 100, img.height() - 100) do |crop|
        crop.resize(crop.width() * 0.9, crop.height() * 0.9) do |small|
            small.save(ARGV[1])
        end
    end
end

RMagick

RMagick is a popular Ruby extension, wrapping the ImageMagick image processing library.

#!/usr/bin/ruby

require 'rubygems'
require 'RMagick'
include Magick

im = ImageList.new(ARGV[0])

im = im.shave(100, 100)
im = im.scale(0.9)
kernel = [-1, -1, -1, -1, 16, -1, -1, -1, -1]
im = im.convolve(3, kernel)
                   
im.write(ARGV[1])
Personal tools