Execution time difference between VB.Net and C# code
-
A few weeks ago there I seen a question regarding converting colors to grayscale equivalents and it caught my interest. I found an article that presented various methods and decided to give it a try in VB.Net. This is my code.
<System.Runtime.CompilerServices.Extension()> _
Public Function ToGrayScale(ByVal img As Image, ByVal method As Methods) As Image
If Not [Enum].IsDefined(GetType(Methods), method) Then
Return Nothing
End If' set the grayscale function to use for conversion
Dim gsfunc As Func(Of Color, Color) = Nothing
Select Case method
Case Methods.Average : gsfunc = AddressOf Average
Case Methods.CorrectingForTheHumanEye_Gimp : gsfunc = AddressOf Gimp
Case Methods.CorrectingForTheHumanEye_BT_601 : gsfunc = AddressOf BT_601
Case Methods.CorrectingForTheHumanEye_BT_709 : gsfunc = AddressOf BT_709
Case Methods.Desaturation : gsfunc = AddressOf Desaturation
Case Methods.DecompositionMax : gsfunc = AddressOf DecompositionMax
Case Methods.DecompositionMin : gsfunc = AddressOf DecompositionMin
Case Methods.SingleColorRed : gsfunc = AddressOf SingleColorRed
Case Methods.SingleColorGreen : gsfunc = AddressOf SingleColorGreen
Case Methods.SingleColorBlue : gsfunc = AddressOf SingleColorBlue
End Select' create a 32bppArgb bitmap from source image
Dim bm As New Bitmap(img.Width, img.Height, Imaging.PixelFormat.Format32bppArgb)
Dim g As Graphics = Graphics.FromImage(bm)
g.DrawImageUnscaled(img, Point.Empty)
g.Dispose()Dim bmdata As System.Drawing.Imaging.BitmapData
' convert 1 row of pixels at a time
Dim pixelcolors(0 To bm.Width - 1) As Int32
For row As Int32 = 0 To bm.Height - 1' lock the row in memory bmdata = bm.LockBits(New Rectangle(0, row, bm.Width, 1), \_ Drawing.Imaging.ImageLockMode.ReadWrite, \_ bm.PixelFormat) ' get row data as array integer color System.Runtime.InteropServices.Marshal.Copy(bmdata.Scan0, pixelcolors, 0, bm.Width) ' convert each pixel color to grayscale using selected method For col As Int32 = 0 To bm.Width - 1 pixelcolors(col) = gsfunc(Color.FromArgb(pixelcolors(col))).ToArgb() Next col System.Runtime.InteropServices.Marshal.Copy(pixelcolors, 0, bmdata.Scan0, bm.Width) bm.UnlockBits(bmdata) ' release memory lock bmdata = Nothing
Next row
Return bm
End Function
Every
-
A few weeks ago there I seen a question regarding converting colors to grayscale equivalents and it caught my interest. I found an article that presented various methods and decided to give it a try in VB.Net. This is my code.
<System.Runtime.CompilerServices.Extension()> _
Public Function ToGrayScale(ByVal img As Image, ByVal method As Methods) As Image
If Not [Enum].IsDefined(GetType(Methods), method) Then
Return Nothing
End If' set the grayscale function to use for conversion
Dim gsfunc As Func(Of Color, Color) = Nothing
Select Case method
Case Methods.Average : gsfunc = AddressOf Average
Case Methods.CorrectingForTheHumanEye_Gimp : gsfunc = AddressOf Gimp
Case Methods.CorrectingForTheHumanEye_BT_601 : gsfunc = AddressOf BT_601
Case Methods.CorrectingForTheHumanEye_BT_709 : gsfunc = AddressOf BT_709
Case Methods.Desaturation : gsfunc = AddressOf Desaturation
Case Methods.DecompositionMax : gsfunc = AddressOf DecompositionMax
Case Methods.DecompositionMin : gsfunc = AddressOf DecompositionMin
Case Methods.SingleColorRed : gsfunc = AddressOf SingleColorRed
Case Methods.SingleColorGreen : gsfunc = AddressOf SingleColorGreen
Case Methods.SingleColorBlue : gsfunc = AddressOf SingleColorBlue
End Select' create a 32bppArgb bitmap from source image
Dim bm As New Bitmap(img.Width, img.Height, Imaging.PixelFormat.Format32bppArgb)
Dim g As Graphics = Graphics.FromImage(bm)
g.DrawImageUnscaled(img, Point.Empty)
g.Dispose()Dim bmdata As System.Drawing.Imaging.BitmapData
' convert 1 row of pixels at a time
Dim pixelcolors(0 To bm.Width - 1) As Int32
For row As Int32 = 0 To bm.Height - 1' lock the row in memory bmdata = bm.LockBits(New Rectangle(0, row, bm.Width, 1), \_ Drawing.Imaging.ImageLockMode.ReadWrite, \_ bm.PixelFormat) ' get row data as array integer color System.Runtime.InteropServices.Marshal.Copy(bmdata.Scan0, pixelcolors, 0, bm.Width) ' convert each pixel color to grayscale using selected method For col As Int32 = 0 To bm.Width - 1 pixelcolors(col) = gsfunc(Color.FromArgb(pixelcolors(col))).ToArgb() Next col System.Runtime.InteropServices.Marshal.Copy(pixelcolors, 0, bmdata.Scan0, bm.Width) bm.UnlockBits(bmdata) ' release memory lock bmdata = Nothing
Next row
Return bm
End Function
Every
Well, Converting between languages in ASP.Net projects, cannnot ever produce a runtime performance. The reason is simple: they all compile to the same CRL module. You could compile it in C++(unmanaged),with significant improvements in speed, but I am not sure if the latest C++/MFC version supportsthe language constructs you use in your source code. Regards, :)
Bram van Kampen
-
Well, Converting between languages in ASP.Net projects, cannnot ever produce a runtime performance. The reason is simple: they all compile to the same CRL module. You could compile it in C++(unmanaged),with significant improvements in speed, but I am not sure if the latest C++/MFC version supportsthe language constructs you use in your source code. Regards, :)
Bram van Kampen
Thank you for the response. This is currently a WinForm project. I did not expect to observe a significant difference or any at all. However, I did and that is what prompted me to post my question. There does appear to be some difference in IL generated from C# and that from the VB code when viewed using Reflector. However I do not understand IL, so I don't know what those differences amount to. I was mainly wondering if anyone else had ever observed such behavior and if there was a reason for it. I am sure you correct that writing it in C++ would give a significant performance boost, and this might be another excuse to venture into C land again. It has been 8 months since my last vist and I think all the wounds to my ego have healed. :sigh:
-
A few weeks ago there I seen a question regarding converting colors to grayscale equivalents and it caught my interest. I found an article that presented various methods and decided to give it a try in VB.Net. This is my code.
<System.Runtime.CompilerServices.Extension()> _
Public Function ToGrayScale(ByVal img As Image, ByVal method As Methods) As Image
If Not [Enum].IsDefined(GetType(Methods), method) Then
Return Nothing
End If' set the grayscale function to use for conversion
Dim gsfunc As Func(Of Color, Color) = Nothing
Select Case method
Case Methods.Average : gsfunc = AddressOf Average
Case Methods.CorrectingForTheHumanEye_Gimp : gsfunc = AddressOf Gimp
Case Methods.CorrectingForTheHumanEye_BT_601 : gsfunc = AddressOf BT_601
Case Methods.CorrectingForTheHumanEye_BT_709 : gsfunc = AddressOf BT_709
Case Methods.Desaturation : gsfunc = AddressOf Desaturation
Case Methods.DecompositionMax : gsfunc = AddressOf DecompositionMax
Case Methods.DecompositionMin : gsfunc = AddressOf DecompositionMin
Case Methods.SingleColorRed : gsfunc = AddressOf SingleColorRed
Case Methods.SingleColorGreen : gsfunc = AddressOf SingleColorGreen
Case Methods.SingleColorBlue : gsfunc = AddressOf SingleColorBlue
End Select' create a 32bppArgb bitmap from source image
Dim bm As New Bitmap(img.Width, img.Height, Imaging.PixelFormat.Format32bppArgb)
Dim g As Graphics = Graphics.FromImage(bm)
g.DrawImageUnscaled(img, Point.Empty)
g.Dispose()Dim bmdata As System.Drawing.Imaging.BitmapData
' convert 1 row of pixels at a time
Dim pixelcolors(0 To bm.Width - 1) As Int32
For row As Int32 = 0 To bm.Height - 1' lock the row in memory bmdata = bm.LockBits(New Rectangle(0, row, bm.Width, 1), \_ Drawing.Imaging.ImageLockMode.ReadWrite, \_ bm.PixelFormat) ' get row data as array integer color System.Runtime.InteropServices.Marshal.Copy(bmdata.Scan0, pixelcolors, 0, bm.Width) ' convert each pixel color to grayscale using selected method For col As Int32 = 0 To bm.Width - 1 pixelcolors(col) = gsfunc(Color.FromArgb(pixelcolors(col))).ToArgb() Next col System.Runtime.InteropServices.Marshal.Copy(pixelcolors, 0, bmdata.Scan0, bm.Width) bm.UnlockBits(bmdata) ' release memory lock bmdata = Nothing
Next row
Return bm
End Function
Every
This is a really good job for a profiler, the difference may be something subtle like the way the C# compiler generates MSIL versus the VB one. Yes, even a line-for-line translation of exactly the same code as you've done above will have different MSIL (in contrast to what your other answer has given). There are some performance improvements you can implement above. You convert the pixel data from an integer, to a color, back to an integer, then back to a color, then back to an integer Pixel Data->ToARGB->BT_601->Convert.ToInt32->FromARGB->ToArgb In my mind it would be simpler to use bit level math for this. Make the BT_601 take the integer data, like:
private static int BT_601(int c)
{
int gs = (((0x00FF0000 & c) >> 16) * 0.299) << 16) +
(((0x0000FF00 & c) >> 8 ) * 0.587) << 8) +
(((0x000000FF & c) * 0.114);
return (0xFF000000 & c) | (gs << 16) | (gs << 8) | gs;
}Which is much faster compiler wise than trying to use all those conversions and function calls. You may also want to do LockBits twice, once with read permission (not read/write) and the next time with write permission (not read). The one-way access helps optimize access to the buffer. You can also get about a 2 time increase by using pointers and unsafe code. You can also try to wrap your for loop in an unchecked block, which will keep the framework from doing bounds checks each time you access the array.
-
This is a really good job for a profiler, the difference may be something subtle like the way the C# compiler generates MSIL versus the VB one. Yes, even a line-for-line translation of exactly the same code as you've done above will have different MSIL (in contrast to what your other answer has given). There are some performance improvements you can implement above. You convert the pixel data from an integer, to a color, back to an integer, then back to a color, then back to an integer Pixel Data->ToARGB->BT_601->Convert.ToInt32->FromARGB->ToArgb In my mind it would be simpler to use bit level math for this. Make the BT_601 take the integer data, like:
private static int BT_601(int c)
{
int gs = (((0x00FF0000 & c) >> 16) * 0.299) << 16) +
(((0x0000FF00 & c) >> 8 ) * 0.587) << 8) +
(((0x000000FF & c) * 0.114);
return (0xFF000000 & c) | (gs << 16) | (gs << 8) | gs;
}Which is much faster compiler wise than trying to use all those conversions and function calls. You may also want to do LockBits twice, once with read permission (not read/write) and the next time with write permission (not read). The one-way access helps optimize access to the buffer. You can also get about a 2 time increase by using pointers and unsafe code. You can also try to wrap your for loop in an unchecked block, which will keep the framework from doing bounds checks each time you access the array.
Ron, Thank you for the well deserved kick in the pants on optimizing the individual grayscale calculations. :) I was too pleased with myself for figuring out that I could read/write the color as an integer versus the separate alpha, red, green, blue bytes and performance boost that yielded as those individual bytes are then re-assembled to recreate that integer value in the Color structure. As I am writing this, I have realized that my logic (and attempt to optimize) was completely flawed from the start as it was all premised on creating and using a Color structure. There is absolutely no need for this structure other than as a pretty box. Oh-well, back to the drawing board. :-O I have never used a profiler, but I will look into it. Unless someone else has an explanation, I will have to chalk this up to the VB compiler optimizing this code segment better than the C# compiler. Thanks again, you made me re-think this and also made me realize that I have become susceptible to falling for the shiny box.:~