Different Performance when Saving different Formats

I have notices that saving jpeg or tiff files in CVB is considerably slower than saving bmp images. Is the compression the only reason? What can I do if I need to save my images faster?

Hi @taserface! Haven’t seen you in a while here… :slightly_smiling_face:

Whether or not I can help you depends on several things:

  • What kind of images are you working with (mono/RGB? most importantly: how many bits per pixel?)
  • What rates (how many images per second) do you need to save?
  • Are you bound to a certain format? Do you have a preference for bitmap formats or would a streaming format also be an option?

I am working with 8 MPixel monochrome images with 10 bits per pixel of which I’ll need to save on average about 12 images per second. Streaming is not an option as it makes accessing individual images more difficult.

I see…
I have played around a little and wrote a small test program to test different formats. In order to be able to benchmark all formats I have decided to use 8bpp, but adapting it to different bit-depths should be fairly easy. For simplicity I have used our new .Net wrapper (see here). The figures are as follows:

Format Time to save 1 MPixel mono Time to save 1 MPixel RGB
BMP 9.46 ms 27.03 ms
MIO 10.89 ms 25.75 ms
JPG 111.08 ms 155.86 ms
TIF 82.74 ms 109.63 ms
PNG 136.19 ms 275.99 ms
J2K 269.17 ms 1368.79 ms

Now, there is indeed a way to speed things up for all formats except BMP and MIO. To understand how one has to know that BMP and MIO are being saved directly by the CVCImg.dll, while all the other formats are catered to by the CVCFile.dll. If you call WriteImageFile in the CVCImg.dll, the function looks at the extension of the target file name and if that extension points to anything else but BMP or MIO, it’ll dynamically load the CVCFile.dll, pass the job over to that DLL and unload it once the file has been saved. This recurring saving and unloading of files can become a bit of a performance hog, so one workaround for you might be to make sure that the CVCFile.dll is not being unloaded between images. To do so, you simply need to call LoadLibrary and not call FreeLibrary before you have saved all the images.

To access those two functions in C# you will of course need to import them first:

[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string fileName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);

Then you can

...
var module = LoadLibrary("CVCFile.dll");
...
// save your images here
...
FreeLibrary(module)

When benchmarking with that fix in place the numbers are as follows (at leas on my machine):

Format Time to save 1 MPixel mono Time to save 1 MPixel RGB
BMP 8.70 ms 28.25 ms
MIO 8.26 ms 27.36 ms
JPG 26.23 ms 74.50 ms
TIF 2.69 ms 26.76 ms
PNG 59.41 ms 193.48 ms
J2K 182.76 ms 1300.56 ms

The differences for BMP and MIO compared to the previous measurement are probably just fluctuations that would be averaged out when performing more measurements. The increase in speed for the CVCFile.dll formats, however, is notable - at least for all formats except JPEG2000 which generally is a very computationally costly format. Speed may of course also vary with images content for the compressing formats (i.e. all except BMP and MIO), but judging from these numbers your chances are good to reach the required 12 fps with TIFF.

If the LoadLibrary/FreeLibrary workaround is not enough, however, I might be out of options here. Streaming formats can - with the help of hardware accelerated codecs - often reach significantly higher throughput, but these capabilities cannot be leveraged by WriteImageFile - one would need to code the compression in CUDA or OpenCL to reach similar speed. Plus streaming formats are usually limited to 8 bits per pixel. In summary: If the workaround is not fast enough for you you might need hardware acceleration.

BTW: You will only see these differences if you are not using the Image and/or Display control in your application. Both controls are linked against the CVCFile.dll and will keep the DLL open as long as there are instances of these controls active in your application.

1 Like

Almost forgot: This is the code I used:

using Stemmer.Cvb;
using Stemmer.Cvb.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
  class Program
  {
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);

    static void Main(string[] args)
    {
      // this 
      var handle = LoadLibrary("CVCFile.dll");

      using (var img = new Image(new Size2D(1024, 1024), 1))
      {
        // produce something that looks nice
        var plane0 = img.Planes[0].GetLinearAccess<byte>();
        var plane1 = img.Planes[1].GetLinearAccess<byte>();
        var plane2 = img.Planes[2].GetLinearAccess<byte>();
        for (int y = 0; y < img.Height; ++y)
          for (int x = 0; x < img.Width; ++x)
          {
            plane0[x, y] = (byte)(x & 0xff);
            plane1[x, y] = (byte)(y & 0xff);
            plane2[x, y] = (byte)((x + y) & 0xff);
          }

        // benchmark saving in different formats
        const int numCycles = 10;
        var formats = new string[] { "bmp", "mio", "jpg", "tif", "png", "j2k" };
        var stopwatch = new StopWatch(StopWatchMode.SingleCPU);
        foreach (var fmt in formats)
        {
          stopwatch.Start();
          for (int i = 0; i < numCycles; ++i)
            img.Save(@"w:\test." + fmt);
          var ms = stopwatch.TimeSpan.TotalMilliseconds / numCycles;
          Console.WriteLine("Format {0}: {1} ms per image of {2:F3} MB", fmt, ms, img.Width * img.Height * img.Planes.Count / 1024.0 / 1024.0);
        }      
      }

      FreeLibrary(handle);
    }
  }
}

Hi @illusive,

Yes, that helps. With the LoadLibrary workaround I get about 15 fps on my machine which is good enough for the moment. But I don’t understand why the CVCFile.dll gets loaded dynamic in the first place - isn’t it kind of obvious tht this slows things down?

You do have a point there…

The decision to load the CVCFile.dll dynamically roughly followed this train of thought:

  1. The CVCFile.dll has numerous build-time dependencies (basically every format that it is capable of saving introduces a dependency on at least one library) which we did not want to burden the CVCImg.dll with. So it made sense to us to gather all these in a separate DLL - that is how the CVCFile.dll came to exist.
  2. On the other hand, the CVCFile.dll has a dependency on the CVCImg.dll - so there would be a cyclic run-time and build-time dependency which we considered unhealthy and resolved by loading the CVCFile.dll dynamically

You are right that the repetitive loading/unloading today is a bottleneck. When the design was decided upon, this was quite different and saving in excess of 10 MB per second as single image files simply was not a use case to be considered back then because of all the other bottlenecks involved (CPU speed, disk speed) so that spending an extra few milliseconds wasn’t considered a problem.

1 Like

Having said that, it’s possible we’ll reconsider that design in the future… :wink: