2011-12-08 26 views
6

C#コードを最適化しておくと、構造要素のリストがあると、各挿入が完全なコピーになります。構造体への参照をC#リストに追加する

私は単純にポインタのリストを保持していますので、リストを構造体への参照リストとして持つことで、C#を使って同じことができるかどうか疑問に思っていました。 残念ながら、構造はXNAライブラリ(Vector3、Matrixなど)の一部であるため、クラスに変換することはできません どのような場合でも、フォーマットと使用方法はどういう形になりますか?

ありがとうございました。

+1

構造体を格納するクラスを作成します。早期の最適化には十分注意してください。ポインタの配列はキャッシュの局所性が悪いです。 –

+2

構造体はどれくらいの大きさのリファレンスですか?構造は小さいと考えられる。もしコピー時間があなたのボトルネックであるリファレンスよりもはるかに大きければ、最初は構造体を使いたくないかもしれません。詳細を教えていただけますか? –

+0

まあ、参照は4バイトしかかからないので、浮動小数点または整数より大きなものはすでにそれより大きいです。あなたが数学をすると、たとえ私が非常に控えめで、単純なVector4しか保存していないとしても、比率はすでに4:1であり、これは大きな構造に近いものではありません。 – Adi

答えて

5

いいえ、基本的には、オプション:

  • それ
  • は、配列を使用し、それをラップしたクラス(基本的に、手動ボクシング)
  • を書くクラス(あなたはまだあなたができないと述べました)
  • ボックスを使用して、 に直接アクセスする(変数にコピーすることなく)。これは配列の項目に直接話しています(コピーなし)

最後の例として、

if(arr[idx].X == 20) SomeMethod(ref arr[idx]); 

.X、及びのsomeMethod内の任意の使用の両方が、アレイではなく、コピーに値を直接アクセスしています。これは、リストではなくベクトル(配列)でのみ可能です。

理由の1つは、構造体への参照のリストが不可能であることです。リスト内に変数のアドレスをスタックに格納することができます。配列は通常、スタック上の変数よりも残っているので、それは馬鹿げて危険です。

+0

私は、Cのスタック上の構造体への参照を意図せずに格納することで、作成できる面白いバグのいくつかを覚えています。 –

+1

Cでは、本当にすべきではないことがたくさんあります。 –

+0

@Ramhound特に、ハッカーがあなたがしてはいけないことを発見し、破損したスタックにデータを注入してアプリケーションを乗っ取ると、 –

5

C#の構造体への格納可能な参照は作成できませんが、値型の参照型ラッパーを作成できます。つまり、メモリのコピーに伴うオーバーヘッドは、小さな構造では高くならないということです。あなたのプロファイリングはこれが問題であることを示しましたか?


以下は、参照型でラップされた値型の例です。これは、特定の値へのすべてのアクセスがラッピング参照型を介している場合にのみ機能することに注意してください。これは(公共分野の​​ため)断熱の標準規則に違反しますが、これは少し特殊なケースです。

public sealed class Reference<T> 
    where T: struct 
{ 
    public T Value; 

    public Reference(T value) 
    { 
     Value = value; 
    } 
} 

注目に値するもう一つは、その内容がnull非許容されているものの参考ラッパー自体は、null値を取ることができるということです。また、必要に応じて暗黙的または明示的な変換演算子を追加して、これをより透過的にすることもできます。

+0

+1はプロファイリングデータについて質問します。 – Scott

0

構造体が単純な型で作られている場合、ポインタの配列を作ることができます。 C#のポインタは、あなたのような場合には非常に便利です。しかし、制限があります。元の構造はArrayに格納され、List<>には格納されていない必要があります。 unsafeビルドフラグでコンパイルされた以下の例を見てください。

[StructLayout(LayoutKind.Sequential)] 
public struct Vec3 
{ 
    public double X, Y, Z; 
    public double Mag { get { return Math.Sqrt(X * X + Y * Y + Z * Z); } } 
} 

public unsafe class Vec3ArrayProxy 
{ 
    Vec3*[] ptr = null; //internal array of pointers 

    public Vec3ArrayProxy(Vec3[] array) 
    { 
     ptr = new Vec3*[array.Length]; //allocate array 
     fixed (Vec3* src = array) //src holds pointer from source array 
     { 
      for (int i = 0; i < array.Length; i++) 
      { 
       ptr[i] = &src[i]; //take address of i-th element 
      }     
     } 
    } 

    public Vec3ArrayProxy(Vec3ArrayProxy other) 
    { 
     //just use all the existing pointers 
     ptr = (Vec3*[])other.ptr.Clone(); 
     //or I could say: 
     //ptr = other.ptr; 
    } 
    // Access values with index 
    public Vec3 this[int index] 
    { 
     get { return *ptr[index]; } 
     set { *ptr[index] = value; } 
    } 
    public int Count { get { return ptr.Length; } } 
    // Access the array of pointers 
    public Vec3*[] PtrArray { get { return ptr; } } 
    // Copy the values of original array into new array 
    public Vec3[] ToArrayCopy() 
    { 
     Vec3[] res = new Vec3[ptr.Length]; 
     for (int i = 0; i < res.Length; i++) 
     { 
      res[i] = *ptr[i]; 
     } 
     return res; 
    } 

} 


unsafe class Program 
{ 
    static void Main(string[] args) 
    { 
     const int N = 10; //size of array 

     // Allocate array in memory 
     Vec3[] array = new Vec3[N]; 

     // Assign values into array 
     for (int i = 0; i < N; i++) 
     { 
      array[i] = new Vec3() { X = i, Y = 0, Z = 0 }; 
     } 

     //Build proxy to array (with pointers) 
     Vec3Array A = new Vec3Array(array); 
     // Reference the same pointers as A 
     Vec3Array B = new Vec3Array(A); 

     // Change the original array 
     array[4].X = -4; 

     // Or change via a copy 
     A.PtrArray[5]->Y = -5; 

     // Or assign a new value 
     B[0] = B[9];    

     // Show contents of array via proxy A 
     Console.WriteLine("{0,-6}|{1,6}|{2,6}|{3,6}|{4,6}", 
      "i", "X", "Y", "Z", "Mag"); 
     for (int i = 0; i < N; i++) 
     { 
      Console.WriteLine("{0,6}|{1,6:F2}|{2,6:F2}|{3,6:F2}|{4,6:F3}", 
       i + 1, A[i].X, A[i].Y, A[i].Z, A[i].Mag); 
     } 

    } 
} 

ロングコードのため申し訳ありませんが、私は構造体のポインタのすべての機能を見せたかったです。List<>が動作しない理由は、リスト要素へのポインタを取ることができないためです。それによってはあなたが一度List<>上の反射をした後、それは、動作し

static T[] ExtractArray(List<T> list) 
    { 
     //list.TrimExcess(); 
     var t = list.GetType(); 
     var items = t.GetField("_items", 
      BindingFlags.NonPublic | BindingFlags.Instance); 
     return items.GetValue(list) as T[]; 
    } 

原油:あなたは本当に、本当に、本当にList<>を使用しなければならない場合は、次のコードでプライベートフィールド_itemsから配列を抽出結果を静的フィールドにキャッシュし、そのたびにitems.GetValue(list)を呼び出すことができます。

+0

これは、プロキシの作成範囲を超えて配列の位置を固定しないため、GCの実行中に配列を移動するとクラッシュする恐れがあります。 –

+0

@DanBryant - 興味深い。 GCが元のアレイを収集し、クラッシュをデモするように強制するにはどうすればよいですか? – ja72

+0

配列を割り当てる直前に小さなオブジェクトを割り当ててから、配列を割り当てた後にそれらを参照することができます(GCはしばらくrootを残しておく必要があることを知っています)、参照を止めてGCを強制します.Collect 。私はこれがGen0のヒープコンパクションを引き起こし、おそらくGen1への配列のプロモーションさえも信じます。いずれかがメモリ内の配列を移動する必要があります。クラッシュすることは保証されません。それは単に静かにメモリを壊すかもしれません。新しくGCd空間に十分な大きさの配列を割り当て、元のポインタにアクセスすると、それを見ることができます。 –

関連する問題