2011-12-08 29 views
6

Tratando de mantener mi código C# optimizado, encontré que si tengo una lista de elementos de estructura, cada inserción sería una copia completa, algo que me gustaría evitar.Añadiendo referencia a estructuras en listas C#

En C++ simplemente mantendría una lista de punteros y entonces me preguntaba si puedo hacer lo mismo usando C#, tal vez teniendo la lista como una lista de referencias a la estructura. Desafortunadamente la estructura no se puede convertir en una clase ya que es parte de la biblioteca XNA (Vector3, Matrix, etc ...) En cualquier caso, ¿cómo se vería el formato y el uso si fuera posible?

Gracias.

+1

Crea una clase que almacene la estructura. Tenga mucho cuidado con la optimización prematura, las matrices de punteros tienen una localidad de memoria caótica pésima. –

+2

¿Cuánto más grande que una referencia es la estructura? Se supone que las estructuras son pequeñas; si son mucho más grandes que una referencia de que el tiempo de copiado es su cuello de botella, es posible que no desee utilizar una estructura en primer lugar. ¿Puedes dar más detalles? –

+0

Bueno, la referencia solo toma 4 bytes, por lo que cualquier elemento más grande que un flotante o entero ya es más grande que eso. Si haces los cálculos, incluso si soy muy modesto y solo almacena un Vector4 simple, la relación ya es 4: 1, y esto no es nada cerca de una estructura grande. – Adi

Respuesta

5

No, básicamente. Opciones:

  • uso de una clase (que ya ha dicho que no se puede)
  • caja en la que
  • escribir una clase que lo envuelve (en esencia, el boxeo manual)
  • utilizar una matriz, y acceda a él solo directamente en la matriz por índice (sin copiar en una variable); esto está hablando directamente con el elemento en la matriz (sin copia)

Como un ejemplo de la última;

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

Tanto el .X como cualquier uso dentro de SomeMethod acceden al valor directamente en el arreglo, no a una copia. Esto solo es posible con vectores (matrices), no listas.

Una razón por la que no es posible una lista de referencias a estructuras: me permitiría almacenar, en la lista, la dirección de una variable en la pila; la matriz suele sobrevivir a la variable en la pila, por lo que sería ridículamente inseguro

+0

Recuerdo algunos de los divertidos errores que puedes crear almacenando involuntariamente referencias a estructuras en la pila en C. –

+1

Puedes hacer muchas cosas en C que no deberías hacer. –

+0

@Ramhound, especialmente cuando un pirata informático descubre lo que no debería haber hecho e inyecta datos en su pila corrupta para secuestrar la aplicación. –

5

No puede crear referencias que se puedan cargar a estructuras en C#, pero podría crear un contenedor de tipo de referencia para su tipo de valor. Dicho esto, la sobrecarga asociada con la copia de la memoria no va a ser alta para una estructura pequeña. ¿Su perfil ha demostrado que esto es un problema?


A continuación se muestra un ejemplo de un tipo de valor envuelto en un tipo de referencia. Tenga en cuenta que esto solo funciona si todo el acceso a un valor particular es a través del tipo de referencia de ajuste. Esto viola las reglas estándar de aislamiento (debido al campo público), pero este es un caso un poco especial.

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

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

Otra cosa a destacar es que la referencia propio envoltorio puede asumir el valor nulo, aunque su contenido es no anulable. También puede agregar operadores de conversión implícitos o explícitos para hacer esto más transparente, si lo desea.

+0

+1 para preguntar acerca de los datos de creación de perfiles. – Scott

0

Si las estructuras están hechas de tipos simples, entonces puede hacer una matriz de punteros. Sí, los punteros en C# son muy útiles para casos como el suyo. Sin embargo, hay limitaciones. Las estructuras originales deben almacenarse en un Array y no en un List<>. Mire el siguiente ejemplo compilado con unsafe indicador de compilación.

[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); 
     } 

    } 
} 

Disculpa el código largo, pero quería mostrar todas las características de struct pointers.La razón por la que List<> no funcionará es porque no puede tomar un puntero a un elemento de lista. Si realmente realmente realmente debe utilizar un List<>, a continuación, extraer el conjunto del ámbito privado _items con el siguiente código:

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[]; 
    } 

crudo que sea, funciona y una vez que haya hecho la reflexión sobre List<> una vez, puede almacenar en caché los resultados en un campo estático y solo llama al items.GetValue(list) cada vez.

+0

Esto no anota la ubicación de la matriz más allá del alcance de la creación del proxy y, por lo tanto, puede bloquearse de maneras horribles cuando la matriz se mueve durante la GC. –

+0

@DanBryant - interesante. ¿Cómo puedo forzar al GC a recopilar el arreglo original y demo la falla? – ja72

+0

Puede intentar asignar un grupo de objetos pequeños justo antes de asignar el conjunto, luego hacer referencia a ellos después de asignar el conjunto (para que el GC sepa que tuvieron que permanecer enraizados durante un tiempo), luego dejar de hacer referencia a ellos y forzar un GC con GC. Recoger . Creo que esto disparará una compactación de Gen0 y posiblemente incluso promocionará la matriz a Gen1; cualquiera de estos debe mover la matriz en la memoria. Tenga en cuenta que no se garantiza que se bloquee; puede simplemente corromper la memoria en silencio. Si asigna una matriz lo suficientemente grande en el espacio nuevo GCd, luego acceda a sus punteros originales, puede observarlo. –