本当にunsafeコードが欲しくなる事例
以下は、本当にunsafeコードが欲しくなる事例として作成したものである。24bitのBMPファイルを読み込んで、ネガ反転処理を行うものである。まず、.NET Frameworkのクラス・ライブラリを活用した正統派のソースをご覧いただこう。
1: using System;
2: using System.IO;
3: using System.Drawing;
4:
5: namespace ConsoleApplication4
6: {
7: class Class1
8: {
9: private static void NegativeBMP( string inputFileName, string outputFileName )
10: {
11: Bitmap bitmap = new Bitmap( inputFileName );
12: for( int y=0; y<bitmap.PhysicalDimension.Height; y++ )
13: {
14: for( int x=0; x<bitmap.PhysicalDimension.Width; x++ )
15: {
16: Color c = bitmap.GetPixel( x, y );
17: bitmap.SetPixel( x, y, Color.FromArgb((byte)~c.R,(byte)~c.G,(byte)~c.B) );
18: }
19: }
20: bitmap.Save( outputFileName );
21: }
22: static void Main(string[] args)
23: {
24: const string inputFileName = "c:\\test1.bmp";
25: const string outputFileName = "c:\\test2.bmp";
26: NegativeBMP( inputFileName, outputFileName );
27: }
28: }
29: } |
|
BMPファイルのネガ反転処理を行うサンプル・プログラム9 |
.NET Frameworkのクラス・ライブラリにあるBitmapクラスを使用して各ピクセルの反転を行っているバージョン。 |
次に、unsafeコードを用いた例をご覧いただこう。
1: using System;
2: using System.IO;
3: using System.Runtime.InteropServices;
4:
5: namespace ConsoleApplication3
6: {
7: [StructLayout(LayoutKind.Sequential,Pack=1)]
8: struct BITMAPFILEHEADER
9: {
10: public ushort bfType;
11: public uint bfSize;
12: public ushort bfReserved1;
13: public ushort bfReserved2;
14: public uint bfOffBits;
15: }
16: [StructLayout(LayoutKind.Sequential,Pack=1)]
17: struct BITMAPINFOHEADER
18: {
19: public uint biSize;
20: public int biWidth;
21: public int biHeight;
22: public ushort biPlanes;
23: public ushort biBitCount;
24: public uint biCompression;
25: public uint biSizeImage;
26: public int biXPelsPerMeter;
27: public int biYPelsPerMeter;
28: public uint biClrUsed;
29: public uint biClrImportant;
30: public const int BI_RGB = 0;
31: };
32:
33: class Class1
34: {
35: unsafe private static void NegativeBMP( string inputFileName, string outputFileName )
36: {
37: FileStream inputFile = new FileStream(inputFileName,FileMode.Open);
38: byte [] fileImage = new byte[inputFile.Length];
39: inputFile.Read(fileImage,0,(int)inputFile.Length);
40: inputFile.Close();
41:
42: fixed( byte * basePtr = &fileImage[0] )
43: {
44: BITMAPFILEHEADER * bitmapFileHeader = (BITMAPFILEHEADER *)basePtr;
45: BITMAPINFOHEADER * bitmapInfoHeader = (BITMAPINFOHEADER * )(basePtr + sizeof(BITMAPFILEHEADER));
46: byte * bits = basePtr + bitmapFileHeader->bfOffBits;
47: if( bitmapInfoHeader->biBitCount != 24 || bitmapInfoHeader->biCompression != BITMAPINFOHEADER.BI_RGB )
48: {
49: Console.WriteLine( "Error: 24bit RGB Bitmap Only");
50: return;
51: }
52: int bytesInLine = (bitmapInfoHeader->biWidth+3)/4*4;
53: int totalBytes = bytesInLine * bitmapInfoHeader->biHeight * 3;
54: for( int i = 0; i<totalBytes; i++ )
55: {
56: *bits = (byte)~*bits;
57: bits++;
58: }
59: }
60:
61: FileStream outputFile = new FileStream(outputFileName,FileMode.Create);
62: outputFile.Write(fileImage,0,fileImage.Length);
63: outputFile.Close();
64: }
65: static void Main(string[] args)
66: {
67: const string inputFileName = "c:\\test1.bmp";
68: const string outputFileName = "c:\\test2.bmp";
69: NegativeBMP( inputFileName, outputFileName );
70: }
71: }
72: } |
|
ポインタを使用してネガ反転処理を行うサンプル・プログラム10 |
BMPファイルを配列に読み込み、そのサイズを求めてから、ポインタ経由で各ピクセルの値の反転を行っているバージョン。処理は複雑になるが、その実行速度はBitmapクラスを用いたバージョンに比べて格段に速くなる。 |
これを実行すると以下のように画像が加工される。結果はどちらも同じになる。
|
入力に用いた24bitのビットマップ画像 |
|
ネガ反転処理を行った出力画像 |
さて、ソースを見比べれば、正統派の方がずっと短く読みやすいことが分かるだろう。しかも、正統派のソースは、24bit BMPに制限されず、Bitmapクラスがサポートするあらゆるデータ型を扱える。それにもかかわらず、unsafeコードが欲しくなる理由は、圧倒的な性能差にある。Pentium 4/1.5GHzのマシンで、正統派のコードは実行に21秒かかったが、unsafe版の方は0.5秒ほどで処理を完了した。
もちろん、ここで取り上げた正統派のソースはあまりにも愚直すぎるので、unsafeコードを使わなくても、低レベルの処理に置き換えることで、かなりの高速化を図ることは可能だ。例えば、byte配列で一気に反転処理を行えば、性能面で大差ないコードを書くこともできるだろう。しかし、このような単純な処理で済まない場合、型を持ったポインタを使用できるunsafeコードの方が、よりエレガントに問題を解決できるだろう。
まとめ
ここまで説明してきたが、やはりunsafeコードは使わなくて済めば使わないに越したことはない機能である。できるだけ使わなくて済むように頑張ろう。
さて、次回は最終回として、これまでの連載で取りこぼしてきたいくつかのトピックを取り上げたいと考えている。
それでは次回もLet's See Sharp!
Insider.NET 記事ランキング
本日
月間