Monday, March 14, 2005

Turn the world on it's head!

Today I have had a bit of a shock. In response to a newsgroup posting I decided to update my article on generating 1 bit per pixel images from 24bpp colour. The original article used a mixture of Bitmap.GetPixel and LockBits to determine the pixel brightness and write the single bit pixel to the image array and I had decided to convert this to a LockBits solution on both sides of the equation for the sake of completeness.
I re-wrote the C# code so that instead of using unsafe pointers it used the Marshal class to read and write the bytes. This made the code identical in function to the VB conversion. After testing the C# code I did a quick conversion to VB and wran the two applications on the same image.
I noticed that the VB application seemed to be faster so I added a diagnostic routing to time how long the central part of the loop, the one that actually does the conversion, took. To make sure the code had a good chunk of data to work with I used an image that was 4000*3200 pixels.
To my great surprise the VB code is consistently faster by over three seconds with the C# doing the loop in around eight seconds and the VB running the functionally identical code in only five.
I have seen instances before where the VB compiler was demonstrably better at generating code than the C# one but have never seen it so clearly shown to be superior at simple tasks than the C# compiler.
The timed code is shown in both C# and VB here:
C#
DateTime dt=DateTime.Now;

//scan through the pixels Y by X
int x,y;
for(y=0;y {
for(x=0;x {
//generate the address of the colour pixel
int index=y*bmdo.Stride+(x*4);
//check its brightness
if(Color.FromArgb(Marshal.ReadByte(bmdo.Scan0,index+2),
Marshal.ReadByte(bmdo.Scan0,index+1),
Marshal.ReadByte(bmdo.Scan0,index)).GetBrightness()>0.5f)
this.SetIndexedPixel(x,y,bmdn,true); //set it if its bright.
}
}

//tidy up
bm.UnlockBits(bmdn);
img.UnlockBits(bmdo);

//show the time taken to do the conversion
TimeSpan ts=dt-DateTime.Now;
VB
'for diagnostics
Dim dt As DateTime = DateTime.Now

'scan through the pixels Y by X
Dim y As Integer
For y = 0 To img.Height - 1
Dim x As Integer
For x = 0 To img.Width - 1
'generate the address of the colour pixel
Dim index As Integer = y * bmdo.Stride + x * 4
'check its brightness
If Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2), Marshal.ReadByte(bmdo.Scan0, index + 1), Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F Then
Me.SetIndexedPixel(x, y, bmdn, True) 'set it if its bright.
End If
Next x
Next y
'tidy up
bm.UnlockBits(bmdn)
img.UnlockBits(bmdo)

'show the time taken to do the conversion
Dim ts As TimeSpan = dt.Subtract(DateTime.Now)
I'll have to compare the IL for the two compiled sections to see where the C# compiler fails to get that extra few ergs.

No comments: