boxing チェッカー
(2008年12月8日追記) オペランドが OperandType.ShortInlineVar のときの読み取り処理が抜けていたバグを修正しました.
XNA で問題となる,頻繁な GC の原因のひとつに,隠れた boxing あります.変なところに嵌りこんでいる boxing コードは意外と探すのが大変です.だったら生成されたアセンブリをディスアセンブルし,box opcode を探してしまえということでプロトタイプを作ってみました.コンソールアプリケーションなので,必要な方は GUI をかぶせてお使い下さい.
コード解説
やっつけツールなのであちこち手を抜いていますが,処理の大まかな流れは DumpBoxingIL を読むことで把握できます.
static void DumpBoxingIL(FileInfo targetFile) { var targetAsm = Assembly.LoadFrom(targetFile.FullName); SetupAssemblyResolver(targetAsm, targetFile.Directory); var scanResult = from method in targetAsm.GetAllMethods() let opcodes = method.GetOpCodes().ToArray() let boxingPoints = from opcodeInfo in opcodes where opcodeInfo.OpCode == OpCodes.Box select opcodeInfo where boxingPoints.Any() select new { Method = method, Opcodes = opcodes, BoxingPoints = boxingPoints.ToArray() }; foreach (var item in scanResult) { Console.WriteLine(item.Method.ReflectedType + "." + item.Method.Name); foreach (var opcodeInfo in item.Opcodes) { if (opcodeInfo.OpCode == OpCodes.Box) { Console.ForegroundColor = ConsoleColor.Red; } Console.WriteLine(" IL_{0:X4}\t{1}", opcodeInfo.PC, opcodeInfo.OpCode); if (opcodeInfo.OpCode == OpCodes.Box) { Console.ForegroundColor = ConsoleColor.White; } } Console.WriteLine(); } }
SetupAssemblyResolver は,アセンブリルックアップの設定を行っています.Xbox 360 用のアセンブリは,XNA GSE のフォルダ以下にある BCL アセンブリを使用するので,その配慮も必要になります.
boxing opcode のスキャンですが,クエリ式に書かれている処理のうち,BCL に含まれず,今回新たに書いたのは次の 2 つです.
- Assembly インスタンスに含まれる全てのメソッドを列挙する GetAllMethods
- MethodInfo インスタンスの IL コードを OpCode と Program Counter の対に分解する GetOpCodes
GetAllMethods は実質的にはただの多重ループです.
static IEnumerable<MethodInfo> GetAllMethods(this Assembly assembly) { var methods = from module in assembly.GetModules() let globalMethods = module.GetMethods() let typeMethods = from type in module.GetTypes() let bindingFlag = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.SetProperty from typeMethod in type.GetMethods(bindingFlag) select typeMethod from method in Enumerable.Concat(globalMethods, typeMethods) select method; return methods; }
GetOpCodes は巨大な switch case です.MethodBody.GetILAsByteArray() で得られたバイト列を先頭から順に調べています.
CIL の OpCode 一覧は,System.Reflection.Emit.OpCodes クラスに静的フィールドとして定義されています.
一覧および,short 値からの逆引き辞書は次のように作ることができます.
var opcodes = typeof(OpCodes).GetFields() .Select(field => (OpCode)field.GetValue(null)) .ToArray(); var opcodedict = opcodes.ToDictionary(op => op.Value);