tonari note

オンラインゲームエンジニアの雑記

静的コンストラクタ

静的クラスのお話 - tonari note

1年ぐらい前に静的クラスの記事書いてたらしいですが、その補足です。
静的コンストラクタの呼び出されるタイミングですが、

  • 最初のインスタンスを作成する前、または静的メンバーが参照される前

です。

using System;

public class Program
{
	public static void Main(string[] args)
	{
		var a1 = new A();
		var a2 = new A();
	}
}

public class A
{
	static A()
	{
		Console.WriteLine("静的コンストラクタ");
	}

	public A()
	{
		Console.WriteLine("コンストラクタ");
	}
}

実行結果

コンストラクタとかよりも前です。
インスタンス化しなくても、静的メソッド呼び出しとかしても呼ばれます。

using System;

public class Program
{
	static Program()
	{
		Console.WriteLine("静的コンストラクタ");
	}

	public static void Main(string[] args)
	{
		Console.WriteLine("Mainメソッド");
	}
}

実行結果

なのでMainメソッドの中身より前に呼ばれます。
ですが、以下の場合のようにメンバーを参照しようとしない場合は呼ばれません。

using System;

public class Program
{
	public static void Main(string[] args)
	{
		var type = typeof(A);
	}
}

public class A
{
	static A()
	{
		Console.WriteLine("静的コンストラクタ");
	}
}

実行結果

MonoBehaviourの抽象化

別にMonoBehaviourじゃなくてもいいのですが、ライブラリなどで提供されている既にあるクラスにインターフェイスのような抽象化を施したい時に、抽象クラスというものがあります。

// インターフェイス
public interface ILoader
{
 IEnumerator Load();
}

// Unityで使う場合はこっち
public abstract class Loader : MonoBehaviour, ILoader
{
 public abstract IEnumerator Load();
}

// 実際の実装
public class HogeLoader : Loader
{
 public IEnumerator Load()
 {
  //...
 }
}

なんか変なサンプルですけど……

これを

public class HogeLoader : MonoBehaviour, ILoader {}

にすると、Unityでは

// インターフェイスはComponentじゃないのでGetComponentできない
GetComponents<ILoader>();

// ので、仕方なく
GetComponents<MonoBehaviour>().Where(_=>(_ as ILoader) != null).Select(...)

みたいな状態になってるのを見たり。
抽象クラスの紹介でした。

Unityでパフォーマンス稼ぐための小ネタ その3

またしても配列ネタです。
今回はArray.Copyについて。

var source = Enumerable.Range(0, 1000).ToArray();
var copied = new int[source.Length];

Array.Copy(source, 0, copied, 0, source.Length);

さて、これでもコピーは可能なのですが、実はプリミティブ型に限りもう少し速度が上がるコピー方法があります。

var source = Enumerable.Range(0, 1000).ToArray();
var copied = new int[source.Length];

Buffer.BlockCopy(source, 0, copied, 0, source.Length * sizeof(int));

Buffer.BlockCopy(MSDN)

Buffer.BlockCopyはプリミティブ型の配列に限り、バイト単位でコピーできるメソッドです。
Array.Copyと違ってバイト数を指定しなければいけない点だけ注意してください。

実行結果
うん、チョットハヤイ。

Unityでパフォーマンス稼ぐための小ネタ その2

Unityでパフォーマンス稼ぐのって結構面倒くさかったりします。
たとえばパフォーマンスの敵と言われているものにガベージコレクションがあります。
時と場合によってGCが走らないようにする回避方法は変わったりしますが、今回はC#でパフォーマンスを稼ぐ一つの方法をご紹介します。

通信やその他ロードなどでバイナリファイルをリアルタイムに読み込むときに以下のようなコードを描いてたりしていると、GCが発生します。

using System;
using System.Linq;

class Program
{
	static void Main(string[] args)
	{
		var start = DateTime.Now;

		var source = Enumerable.Range(0, 100).ToArray();
		var binary = new byte[1024];

		var decode = new int[source.Length];

		start = DateTime.Now;
		var cache = new byte[sizeof(int)];
		for (var roop = 0; roop < 10000; ++roop)
		{
			for (var i = 0; i < source.Length; ++i)
			{
				cache = BitConverter.GetBytes(source[i]);
				binary[i * 4] = cache[0];
				binary[i * 4 + 1] = cache[1];
				binary[i * 4 + 2] = cache[2];
				binary[i * 4 + 3] = cache[3];
			}
			for (var i = 0; i < source.Length; ++i)
			{
				decode[i] = BitConverter.ToInt32(binary, i * 4);
			}
		}
		Console.WriteLine("経過時間 " + (DateTime.Now - start).ToString());
		Console.WriteLine("GC回数 " + GC.CollectionCount(0));
	}
}

実行結果
つらい。

CLRなんかに比べて当然Mono AOTだとGC回数は多くなるので、こういうのは避けたい。
というわけで以下のようにしてみます。

using System;
using System.Linq;

class Program
{
	static void Main(string[] args)
	{
		var start = DateTime.Now;

		var source = Enumerable.Range(0, 100).ToArray();
		var binary = new byte[1024];

		var decode = new int[source.Length];

		start = DateTime.Now;
		for (var roop = 0; roop < 10000; ++roop)
		{
			for (var i = 0; i < source.Length; ++i)
			{
				unsafe
				{
					fixed (byte* p = &binary[i * 4])
						*(int*)p = source[i];
				}
			}
			for (var i = 0; i < source.Length; ++i)
			{
				unsafe
				{
					fixed (byte* p = &binary[i * 4])
						decode[i] = *(int*)p;
				}
			}
		}
		Console.WriteLine("経過時間 " + (DateTime.Now - start).ToString());
		Console.WriteLine("GC回数 " + GC.CollectionCount(0));
	}
}

実行結果
結果はGC回数が大幅に減ってます。

あとAOTとポインタの相性がそこそこ良いっぽくて、BitConverter使うよりさらに速度を期待できます。
見てのとおりunsafeなのでunsafeで、それなりにルールもあるので使いどころは限られますが。

Unityでパフォーマンス稼ぐための小ネタ その1

Unity関係ないといえば無いんですが、今回はLINQメソッドと同じ機能がArrayなりListなりに用意されている場合はそっちのほうが良いときもありますよ、という一例を。

配列を後から持ってくる場合に有効なものです。

using System;
using System.Linq;

class Program
{
	static void Main(string[] args)
	{
		var roopCoount = 10000;
		var start = DateTime.Now;
		var source = Enumerable.Range(0, 100).ToArray();

		start = DateTime.Now;
		for (var roop = 0; roop < roopCoount; ++roop)
		{
			// LINQ Enumerable.Reverse
			source.Reverse().ToArray();
		}
		Console.WriteLine("経過時間 " + (DateTime.Now - start).ToString());
		Console.WriteLine("GC回数 " + GC.CollectionCount(0));

		start = DateTime.Now;
		for (var roop = 0; roop < roopCoount; ++roop)
		{
			// Array.Reverse
			Array.Reverse(source);
		}
		Console.WriteLine("経過時間 " + (DateTime.Now - start).ToString());
		Console.WriteLine("GC回数 " + GC.CollectionCount(0));
	}
}

実行結果

Array.Reverseではインスタンス化が行われないのでGCが行われません。
GC.CollectionCountは開始した時からの累計回数なので、Array.Reverseの方では0回という結果になってます。)
これだけでパフォーマンス結構上がったりします。
まぁLINQの方は毎度ToArrayしてるから当然じゃないか、って感じなんですが結局どこかで評価しなきゃいけないので。

また、

var t = array.Where().Reverse().ToArray();

よりも

var t = array.Where().ToArray();
Array.Reverse(t);

のほうがパフォーマンスが良いです。
実行結果

僕はリアルタイムな読み込みでバイナリを変換する時にエンディアンが違って逆転が必要になった、というパターンのときに使ったりしました。

ハマった

以下のコードをご覧ください。

string str = null;
// ==
str.Fuga();

僕はこのコードを「危ない」と思って以下のように書いたんですね。

if (str != null) 
	str.Fuga();

でもなんか動かない、挙動がおかしい。
で、同僚と探りを入れていくと以下のようなコードが。

static class StringExtensions
{
	public static void Fuga(this string str)
	{
		Console.WriteLine("残念、拡張メソッドだよ");
	}
}

実行結果

nullの時でも正しく動かないといけなかったらしい。
辛かったのでメモ。

C#のforeachな話

foreachについて最近よく聞かれるので。

var ary = new []{1, 2, 3};
foreach(var num in ary)
	Console.WriteLine(num);

配列やらリストやらを順番に処理していく。

この順番にっていうのがミソで、じゃあ順番ってなんやねんってなるわけです。

var ary = new []{1, 2, 3};
var it = ary.GetEnumerator();
while(it.MoveNext())
	Console.WriteLine(it.Current);

実行結果

こんな感じでIEnumeratorやらIEnumerator(T)やらを貰って、MoveNextメソッドを呼び、次の要素があるならその要素を取得する、みたいなことをやってます。

世間ではシーケンスやら言ったりしますが、配列なりListなりDictionaryなりは全部IEnumerableIEnumerable(T)やらを実装してます。
これらIEnumerableの定義はGetEnumerator()メソッドだけです。
つまり配列やリストなどじゃなくても、自前のクラスでこれらを実装すればforeachでなめることができます。

class MySequence : IEnumerable
{
	public IEnumerator GetEnumerator()
	{
		return new MyEnumerator();
	}

	class MyEnumerator : IEnumerator
	{
		private int count = 0;

		public object Current
		{
			get { return (count++).ToString(); }
		}

		public bool MoveNext()
		{
			return count < 3;
		}

		public void Reset()
		{
		}
	}
}

実行結果

ここで面白いのは、C#のforeachは糖衣構文なので、IEnumerableを実装していなくても、IEnumerator GetEnumerator()があれば動くということです。
実行結果
先ほどと違ってMySequenceはIEnumerableを実装していません。
LINQの場合はIEnumerable(T)の拡張メソッドが主になりますが、IEnumerableの拡張メソッドも使われたくない、けどforeachでは使いたいって言う時は上記の手段で解決できます。

さて、foreachにはもう一つ機能があります。
実行結果
EnumeratorにIDisposableを実装すると、エラーが起こった際にそのDisposeを呼んでくれます。

C# 5.0以降では、Unityで使われているC# 3.0と挙動が変わる部分があります。
そちらは岩永さんの所に詳しく書かれていますのでこちらへ。
C#5.0の新機能

foreachは基本的にはwhileに展開されるだけの糖衣構文なのでインデックスが取れない(LINQのSelect使うと取れる)んですが、それなりに便利だと思うので、良い感じに使いたいですぬ。