2013-07-07 10 views
9

私はC#でパーサーコンビネータを最適化することを試しています。シリアル化されたフォーマットがメモリ内のフォーマットと一致する場合、可能な最適化の1つは、インスタンス上または解析されるべきデータの(安全でない)memcpyだけをインスタンス化することです。リフレクションを使用して.Netタイプがメモリ内でどのようにレイアウトされているかを調べる

最適化を適用できるかどうかを動的に判断するために、メモリ内の形式がシリアル化された形式と一致するかどうかを判断するコードを記述します。 (明らかにこれは安全でない最適化であり、微妙な理由のためにはうまくいかないかもしれません。私は実践的なコードでこれを使用するつもりではなく、実験しています)

属性[StructLayout(LayoutKind.Sequential, Pack = 1)]を使用して、メモリ内の順序で宣言の順序を一致させます。私は反射でその属性をチェックしますが、実際にはすべてこの確認は「パディングなし」です。私はまた、フィールドの順序が必要です。 (私は強くそれが発生しやすい非常にエラーになりますので、手動で、フィールド毎FieldOffset属性を指定する必要がないことを好むだろう。)

私はGetFieldsによって返されたフィールドの順序を使用することができます仮定しますが、ドキュメントが明示的にことを呼び出します注文は不特定である。

StructLayout属性でフィールドの順序を強制しているとすれば、その順序を反映する方法はありますか?

編集すべてのフィールドがblittableである必要があります。

+0

これらの属性に反映することで解決できませんか? –

+0

@newStackExchangeInstanceどの属性ですか? –

+1

'LayoutKind.Sequential'は、blittable型だけが構造体に存在する場合にのみ、管理された表現を制御します。区切り不可能な型がある場合、フィールドの順序は実行時に制御されます。例えば。 http://stackoverflow.com/q/14024483/11683を参照してください。 – GSerg

答えて

5

あなたがいる限り、すべてのフィールドは、blittable型であるため、メモリ内の構造体のフィールドの順序を見つけるために反射または任意の他のメカニズムを使用する必要はありませんblittable型のタイプとLayoutKind.Sequential

を使用している場合これは不要です。

LayoutKind.Sequentialで宣言された構造体のblittableフィールドは、フィールドが宣言されている順番でメモリに格納されます。これは、LayoutKind.Sequentialの意味です!

From this documentation

blittable型タイプの場合は、LayoutKind.Sequentialは、マネージメモリ内のレイアウトとアンマネージメモリ内のレイアウトの両方を制御します。非blittable型の場合は、クラスまたは構造体がアンマネージコードにマーシャリングされ、マネージメモリのレイアウトは制御されません。

各フィールドの埋め込み量はわかりません。それを見つけるには、以下を参照してください。 LayoutKind.Autoを使用するときに、フィールドの順序を決定するために

、あるいはフィールド・オフセット任意のレイアウト

を使用するときにそれはあなたが危険なコードを使用して満足している場合は、構造体のフィールドオフセットを見つけるのは比較的簡単だし、にありません反射を使用します。

構造体の各フィールドのアドレスを取得し、構造体の先頭からオフセットを計算するだけです。各フィールドのオフセットを知ることで、順序(およびそれらの間のパディングバイト)を計算できます。最後のフィールドに使用されているパディングバイトを計算するには、sizeof(StructType)を使用して構造体の合計サイズを取得する必要もあります。

次の例は、32ビットと64ビットで動作します。構造体が既に固定されているので、あなたが原因それがスタック上にあることにfixedキーワードを使用する必要はないことに注意してください(あなたがそれをfixedを使用しようとすると、コンパイルエラーが発生します):

using System; 
using System.Runtime.InteropServices; 

namespace Demo 
{ 
    [StructLayout(LayoutKind.Auto, Pack = 1)] 

    public struct TestStruct 
    { 
     public int I; 
     public double D; 
     public short S; 
     public byte B; 
     public long L; 
    } 

    class Program 
    { 
     void run() 
     { 
      var t = new TestStruct(); 

      unsafe 
      { 
       IntPtr p = new IntPtr(&t); 
       IntPtr pI = new IntPtr(&t.I); 
       IntPtr pD = new IntPtr(&t.D); 
       IntPtr pS = new IntPtr(&t.S); 
       IntPtr pB = new IntPtr(&t.B); 
       IntPtr pL = new IntPtr(&t.L); 

       Console.WriteLine("I offset = " + ptrDiff(p, pI)); 
       Console.WriteLine("D offset = " + ptrDiff(p, pD)); 
       Console.WriteLine("S offset = " + ptrDiff(p, pS)); 
       Console.WriteLine("B offset = " + ptrDiff(p, pB)); 
       Console.WriteLine("L offset = " + ptrDiff(p, pL)); 

       Console.WriteLine("Total struct size = " + sizeof(TestStruct)); 
      } 
     } 

     long ptrDiff(IntPtr p1, IntPtr p2) 
     { 
      return p2.ToInt64() - p1.ToInt64(); 
     } 

     static void Main() 
     { 
      new Program().run(); 
     } 
    } 
} 

LayoutKind.Sequential

を使用している場合、フィールド・オフセットを決定するためにあなたの構造体にはLayoutKind.Sequential、あなたが直接オフセットを取得するMarshal.OffsetOf()を使用することができますを使用していますが、これはLayoutKind.Autoない仕事ない場合:

foreach (var field in typeof(TestStruct).GetFields()) 
{ 
    var offset = Marshal.OffsetOf(typeof (TestStruct), field.Name); 
    Console.WriteLine("Offset of " + field.Name + " = " + offset); 
} 

LayoutKind.Sequentialを使用している場合は、unsafeコードを必要としないため、これは明らかに良い方法です。これははるかに短く、事前にフィールドの名前を知る必要はありません。上で述べたように、メモリ内のフィールドの順序を決定する必要はありませんが、どれだけのパディングが使用されているかを知る必要がある場合には便利です。

+0

ありがとう、ポインタの違いを使用して正確に私が必要なもののようなものです。限り、.Net任意の最適化を許可しないフィールドは、楕円形や何かのような... –

+0

t.Iのようなフィールドに&演算子を適用しようとすると、 "与えられた式のアドレスを取ることができません"コンパイラエラーが発生します。 –

+0

@Strilanc私のコードをコピーして貼り付けるとうまくいくので、別の何かをする必要があります。あなたがしていることがうまくいかない理由を尋ねる新たな質問をすることはできますか?ここでコメントすることは不可能です。私は作品を投稿したコードを知っていますし、その中のどこにいても 't.l'(小文字の' l')のコードは含まれていません。 :) –

2

レイアウトの順序と種類を知りたい人の参考にしてください。たとえば、タイプにblittable以外のタイプが含まれている場合。

public static void SortByFieldOffset(this FieldInfo[] fields) { 
     Array.Sort(fields, (a, b) => OffsetOf(a).CompareTo(OffsetOf(b))); 
    } 

    private static int OffsetOf(FieldInfo field) { 
     return Marshal.OffsetOf(field.DeclaringType, field.Name).ToInt32(); 
    } 

MSDNがIsLayoutSequentialに関する有用な情報が含まれています

var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 
fields.SortByFieldOffset(); 

var isExplicit = typeof(T).IsExplicitLayout; 
var isSequential = typeof(T).IsLayoutSequential; 

は、それは私が書いた拡張メソッドを使用しています。

+2

'return fields.OrderBy(OffsetOf).ToArray()'はもう少し簡潔で、起動時には変更できません。 –

関連する問題