程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> wpf大圖片處理速度優化:指針操作,並行操作,幾十倍優化

wpf大圖片處理速度優化:指針操作,並行操作,幾十倍優化

編輯:C#入門知識

我一直用GDI+做Winform 的基於指針的圖片處理,這次下決心全部移到wpf上(主要是顯示布局很方便)
采用的圖片是
2512*3307 的大圖 830萬像素
類庫基於WritableBitmapEx 的wpf版本
函數是我自己寫的擴展方法,只是利用了 writableBitmapEx提供的環境 ,我懶得從頭到尾自己寫了
 
1.標准int32數組遍歷計算 release
0.28s

        unsafe public static void TestGray1(this WriteableBitmap bmp)
        {
            using (var context = bmp.GetBitmapContext())
            {
                int height = context.Height;
                int width = context.Width;
                for (int y = 0; y < height; y++)
                {                   
                    for (int x = 0; x < width; x++)
                    {
                        int pos=y * context.Width + x;
                        var c = context.Pixels[pos];
                        var r = (byte)(c >> 16);
                        var g = (byte)(c >> 8);
                        var b = (byte)(c);
                       
                        var gray = ((r * 38 + g * 75 + b * 15) >> 7);

                        var color=(255 << 24) | (gray << 16) | (gray << 8) | gray;
                        context.Pixels[pos]=color;
                    }
                }
            }
        }

 

2.標准int32指針遍歷計算 release

0.04s


        unsafe public static void TestGray2(this WriteableBitmap bmp)
        {
            using (var context = bmp.GetBitmapContext())
            {
                var ptr = context.Pixels;

                int height = context.Height;
                int width = context.Width;
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        var c = *ptr;
                        var r = (byte)(c >> 16) ;
                        var g = (byte)(c >> 8) ;
                        var b = (byte)(c) ;

                        var gray = ((r * 38 + g * 75 + b * 15) >> 7);

                        var color = (255 << 24) | (gray << 16) | (gray << 8) | gray;
                        *ptr = color;

                        ptr++;
                    }
                }
            }
        }

 

3.colorstruct指針 遍歷計算

0.02 s

應該是已經到極限速度了[除了後面的並行方式],我已經想不出還有什麼方法可以提高處理速度

而且這種方式是最直觀的,最容易理解的處理方式,也便於以後維護


 


    [StructLayout(LayoutKind.Sequential)]
    public struct PixelColor
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Alpha;
    }

 


        unsafe public static void TestGray3(this WriteableBitmap bmp)
        {
            using (var context = bmp.GetBitmapContext())
            {
                var ptr = (PixelColor*)context.Pixels;

                int height = context.Height;
                int width = context.Width;
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        var c = *ptr;
                        var gray = ((c.Red * 38 + c.Green * 75 + c.Blue * 15) >> 7);
                        (*ptr).Green=(*ptr).Red=(*ptr).Blue = (byte)gray;

                        ptr++;
                    }
                }
            }
        }


 

4.作為對比,我又測試了一下 GDI+的 指針處理圖片的速度

0.06s

 

 


        public static unsafe Bitmap ToGray(Bitmap img)
        {
            var rect = new System.Drawing.Rectangle(0, 0, img.Width, img.Height);

            var data = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            var ptr = (ColorType*)data.Scan0.ToPointer();

            var bytes = new Int32[img.Width * img.Height];

            var height = img.Height;
            var width = img.Width;
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    var color = *ptr;
                    var gray = ((color.R * 38 + color.G * 75 + color.B * 15) >> 7);

                    (*ptr).R = (*ptr).G = (*ptr).B = (byte)gray;

                    ptr++;
                 }
            }

            img.UnlockBits(data);

            return img;
        }

 

5.重頭戲來了。我一直對Parallel.For 很迷惑,為什麼他的消耗時間是普通for的好幾倍。今天仔細研究了一下,發現原來是用錯了

0.01秒   release

 筆記本i5cpu,如果台式機的I7會更加強悍,速度會成半成半降低。

 

 

主要是利用了微軟的任務並行庫的循環並行化的方法。

注意:默認的並行循環對於函數體很小的情況是很慢的,這種情況必須用Partitioner 創建循環體,這在MSDN有介紹,是關鍵之中的關鍵

 


        unsafe public static void TestGray5(this WriteableBitmap bmp)
        { 
            using (var context = bmp.GetBitmapContext())
            {
                int height = context.Height;
                int width = context.Width;

                Parallel.ForEach(Partitioner.Create(0, height), (h) =>
                {
                    var ptr = (PixelColor*)context.Pixels;
                    ptr += h.Item1 * width;

                    for (int y = h.Item1; y < h.Item2; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            var c = *ptr;
                            var gray = ((c.Red * 38 + c.Green * 75 + c.Blue * 15) >> 7);
                            (*ptr).Green = (*ptr).Red = (*ptr).Blue = (byte)gray;

                            ptr++;
                        }
                    }

                });

            }
        }


 

感想

1.絕對不要在循環體內使用屬性或函數,很有可能會降低數倍計算速度。

因為屬性本質上是個函數,而在循環體內最好不要再調用函數,如果確實需要用內聯代碼的方式,c#沒有inline,那麼copy代碼吧,反正為了速度。

2. 用指針移位操作 似乎比 直接數組訪問要快10倍啊

我感覺要麼是cache命中的原因,要麼是 數組本身存取被屬性封裝了。相當於又調用了函數。

3.TPL 任務並行庫果真好用,看來微軟早已考慮過大量數據並行的循環優化問題09年,只是我一直用錯了方法,才覺得很慢。

 

天氣真好,寫完代碼後心情舒暢。

 

摘自 苦力熊

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved