user3512524 user3512524 - 2 years ago 82
C# Question

WPF BitmapImage creation extremely slow on Xeon/nVidia Quadro machine

I wrote a WPF program which is graphical browser of things. It displays quite a lot of images in ListView with Image control for each of them. It also allows user to do basic image editing.

I've run this software on many different machines: my dev laptop, my colleague's MacBook with Paralel Desktop, VirtualBox VM with access via Windows Remote Desktop and even on 10 years old laptop. It run very well.

Unfortunately, our first client is design company with worskstations build for CAD design, with Xeon processors and nVidia Quadro 2000 cards.
Now comes the shock: my program is so slow on their machines that it is not usable.

I've used dotTrace and found that the bottleneck is my ImageToImageSourceConverter, which is the most standard one:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Bitmap bmp = value as Bitmap;
if (bmp == null)
return null;

using (MemoryStream memory = new MemoryStream())
{
memory.Seek(0, SeekOrigin.Begin);
bmp.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}


dotTrace showed, that on their machines the line with bitmapImage.EndInit() takes the mot of computing time. It is also done on the ui thread so app freezes for a while. dotTrace tells that the call ends up in clr.dll

To show to the scale: on my laptop this conversion tkaes about 5-10ms, depending on bitmap. On their machines (all of them!) it takes around 800ms, or sometimes even more.

On other machines that is not a problem: it runs fast, freezes are not noticable. Even more, displayed bitmaps are so small (200x200px) that when I change this to be async it was slower, due to thread change overhead.

Does someone have any idea why this is happening?

Things I've tried so far:


  • disabling hardware rendering via RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; - no difference

  • updating the drivers for graphics card: no difference

  • disabling all accelerations in graphics card drivers and Window Compability mode: no difference

  • running as Administrator (why I did this): it runs noticably faster, but still not enough.



The problem is big: we won't get paid if this will not run faster :D

What is even more weird, sometimes it works just fine for few secconds.

My pen drawing is implemented in such a way, that onMouseMove with button presed I create a copy of previous image and draw additional line with a preview color from last mouse position to the new one and keep track of this points. When you release LMB, I draw the lines between points with target color on original image. It may seem slow, but is suprisingly fast on every machine that I tested.

On theirs, when you click and wait for a moment, then move the mouse it runs smoothly. But when you click and move mouse instantly, it freezes and draws straight line between click position and last position, because it does not register points between.

So, is this related to slow loading some dll and then releasing it when it is not used for some time? If so, which one can it be?

Specs of the workstations:
Intel(R) Xeon(R) CPU E5-1650 0 @ 3.20GHz
NVIDIA Quadro 2000 1GB
16 GB RAM

Answer Source

as @Clemens suggested, I have changed the method of convertion between Bitmap and ImageSource. This is my new converter:

  [ValueConversion(typeof(Bitmap), typeof(ImageSource))]
public class BitmapToImageSourceConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Bitmap bitmap = value as Bitmap;
        if (bitmap == null)
            return null;

        var bitmapData = bitmap.LockBits(
            new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);

        var bitmapSource = BitmapSource.Create(
            bitmapData.Width, bitmapData.Height, 96, 96, ConvertPixelFormat(bitmap.PixelFormat), null,
            bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);

        bitmap.UnlockBits(bitmapData);
        return bitmapSource;
    }

    private static System.Windows.Media.PixelFormat ConvertPixelFormat(System.Drawing.Imaging.PixelFormat sourceFormat)
    {
        switch (sourceFormat)
        {
            case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
                return PixelFormats.Bgr24;

            case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                return PixelFormats.Bgra32;

            case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
                return PixelFormats.Bgr32;

            // .. as many as you need...
        }
        return new System.Windows.Media.PixelFormat();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

This method is even 20 times faster on average on some computers. My measurements show, that it does the conversion under 1ms on almost every machine, while the previous converter did that always above 5ms, and this varied between computers from 5 to 50, sometime even 800ms like I said in my question.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download