■ 前回の実験では、構造体の構成によるパフォーマンスの相違を検証してみました。
今回は、同じソースをC#に移植して検証、比較的新しいプログラム言語としてC++よりオブジェクト化に適していると言われるC#の実力を計ってみよう。
public struct stRGB
{
public byte R, G, B;
}
|
public struct stRGB
{
private const float BRK = 0.298912f;
private const float BGK = 0.586611f;
private const float BBK = 0.114478f;
public byte R, G, B;
public void Clear()
{
R = G = B = 0;
}
public float Brightness()
{
return (float)((R + G + B) * (1 / 3));
}
public byte GrayScale()
{
return (byte)(R * BRK + G * BGK + B * BBK);
}
public static stRGB operator +(stRGB A, stRGB B)
{
stRGB res;
try
{
res.R = (byte)(A.R + B.R);
res.G = (byte)(A.G + B.G);
res.B = (byte)(A.B + B.B);
return res;
}
catch
{
res.R = res.G = res.B = 0;
return res;
}
}
public static stRGB operator -(stRGB A, stRGB B)
{
}
public static stRGB operator *(stRGB A, stRGB B)
{
}
public static stRGB operator /(stRGB A, int B)
{
}
} |
前回のソースもVB9(=Visual Studio 2008版)で制作したものですが、まんま同じものをCS9(=C#=Visual Studio 2008版)に移植しただけです。構文はC++のようですが、構造はVBに似ています。
C言語は歴史が古いので、「如何に効率的にコードを生成できるか?」と言うことを主眼に作られている言語なので、プログラマの自制心が問われる言語です。 どのような型にも無責任にキャストできてしまうし、どのような数値もポインタとして渡すことが可能なので、めんど臭くなったらついつい近道をしてしまうので無責任なコードがいっぱい出来てしまいます。
一時期流行になった「バッファ・オーバ・フロー」によるセキュリティホールなどは、関数間の引き渡し時に配列のサイズについて全く責任を持たないC言語の欠陥が原因と言っても過言ではないでしょう。
そのような問題を解決し、構文的にはC言語ライクにして言語側でインチキ出来にくくしたもの、各機能のパッケージ化を容易にしたものがC#ではないかと思われます。
まぁ、C#だからと言っても unsafe とか宣言しちゃうとポインタも使えるし、古いベタベタのコードも書けちゃうので、結局はプログラマの自制心が最終的な歯止めと言うことでしょうがね。
<閑話休題>
で、実際の処理はこちら、
private void button1_Click(object sender, EventArgs e)
{
double ST, ET;
ST = QPF.GetMilliTime();
stRGB[] C = new stRGB[VOL];
stRGB res = C[0];
for (int n = 1; n < VOL; n++)
{
res.R = (byte)((res.R + C[n].R) / 2);
res.G = (byte)((res.G + C[n].G) / 2);
res.B = (byte)((res.B + C[n].B) / 2);
}
ET = QPF.GetMilliTime() - ST;
textBox1.Text = ET.ToString("#,##0.000");
}
|
private void button1_Click(object sender, EventArgs e)
{
double ST, ET;
ST = QPF.GetMilliTime();
stRGB[] C = new stRGB[VOL];
stRGB res = C[0];
for (int n = 1; n < VOL; n++)
{
res = (res + C[n]) / 2;
}
ET = QPF.GetMilliTime() - ST;
textBox1.Text = ET.ToString("#,##0.000");
}
|
例によって、構造体に演算式を含めた場合にはループ内の処理はたったの1行で済んでしまいます。
で、処理時間は、
シンプルな構造体 |
87ms |
高度な構造体 |
456ms |
|
VB9に比較してCS9はシンプルな構造を使うと約2.9倍も速くなります。 でも、構造体が複雑になるとその差は1.5倍程度になり、C#内での動作がより複雑になっているのではないかと想像できます。
※2013/01/18追記 「急いては事をし損じる」と言うわけですね、上記の結果はWindows7(x64)での結果です。 同じWindows7でも32ビット版では結果が異なります。シンプルな構造体のプログラムの32ビット版OSの場合の結果は以下のようになります。
動作させるPCが異なりますので、x64:x86相互での比較はできませんが、32ビット版でのVB:C#の比較では、ナントほとんど差がなくなってしまいました。 コンパイラの問題なのかWOW64が問題なのか、64ビット版ではC#にかなりなメリットがあるようです。
同様に下の文字列操作の32ビット版OSでの結果です。
こちらも、パフォーマンスの差は1%未満で誤差程度、どちらが良いとは言えない数値です。 |
■ 結構C#も速いじゃん、と言う訳で調子に乗ってC言語では全くダメダメだった文字列操作について、VB9 vs CS9で対決!
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A As String = Nothing
Dim ST, ET As Double
ST = QPF.GetMilliTime
For n = 0 To 100000 - 1
A += "/"
Next
ET = QPF.GetMilliTime - ST
Me.TextBox1.Text = ET.ToString("#,##0.000") '+ "ms"
End Sub
|
private void button2_Click(object sender, EventArgs e)
{
double ST, ET;
ST = QPF.GetMilliTime();
string A = null;
for (int n = 0; n < 100000; n++)
{
A += "/";
}
ET = QPF.GetMilliTime() - ST;
textBox1.Text = ET.ToString("#,##0.000");
}
|
ソースは説明の余地なしという感じですね、string A に10万文字の "/" を1文字ずつ連結していくものです。
結果です。
あれれっ? 1.05倍程度しか速くなりません。 やはり、文字列操作についてはVBが「一日の長あり」と言うところでしょうか。 C言語がストリングクラスとか言い始めるずーっと昔にBasic言語は文字列を普通に処理できていました。
別件で解ったことですが、実はC#ではメモリの確保に凄い時間が掛かるのです。 安全なメモリ確保と解放に注力しすぎたのか、ヘタにローカルに配列などを確保すると、そのオーバヘッドを見過ごすことができなくなります。
次回は、C#でのメモリ確保について検証してみたいと考えています。(2013/01/18) |