2010-01-08 16 views
13

así que en C++ es muy fácil. usted quiere que se asigne cualquier clase/estructura en el montón, use nuevo. si lo quieres en la pila, no uses nuevo.C# structs/classes stack/heap control?

en C# siempre usamos la nueva palabra clave, y dependiendo de si se trata de una estructura o una clase, se asigna en la pila o en el montón (las estructuras van a la pila, las clases al montón) y en algunas aplicaciones puede haber una ENORME diferencia de rendimiento cuando se cambia el diseño de modo que solo esos objetos van al montón que realmente pertenecen allí.

Lo que me pregunto es: ¿hay una manera directa de controlar dónde se asigna un objeto independientemente de si se declara como estructura o clase? Sé que los tipos de valores (estructuras) se pueden guardar en cajas para ir al montón (pero el boxeo/unboxing tiene un costo de rendimiento). ¿Hay alguna manera de asignar clases en la pila?

Además, ¿hay algún mecanismo para asignar memoria sin procesar y usar algo así como la ubicación nueva en C++? Sé que esto rompe con la idea de ser administrado, pero puede hacer una gran diferencia en el rendimiento si puede usar su administración de memoria personalizada.

Me encanta C# por su conveniencia, porque es un recolector de basura y otras cosas, pero a veces, al trabajar en el cuello de botella de una aplicación, puede ser conveniente tener más control sobre lo que realmente está sucediendo.

Algún consejo/toques agradables :)

edición: Ejemplo de ejecución:

struct Foo1 
{ 
    public int i; 
    public float f; 
    public double d; 
} 

struct Foo2 
{ 
    public Foo1[] bar; 

    public void Init(){ 
     bar = new Foo1[100]; 
     for (int i = 0; i < 100; i++) 
      bar[i] = new Foo1(); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     DateTime time = DateTime.Now; 
     Foo2[] arr = new Foo2[1000000]; 
     for (int i = 0; i < 1000000; i++) 
     { 
      arr[i] = new Foo2(); 
      arr[i].Init(); 
     } 

     Console.WriteLine((DateTime.Now - time).TotalMilliseconds); 
    } 
} 

Esto toma 1.8 segundos en mi máquina para ejecutar (tenga en cuenta que en realidad sólo hay asignación pasando - sin paso de parámetros)

si Foo1 se cambia de estructura a clase, la ejecución lleva 8.9 segundos. eso es cinco veces más lento

+3

¿Qué te hace creer que hay una * enorme * diferencia de rendimiento entre la asignación de objetos en la pila y el montón? – LBushkin

+0

Me di cuenta cuando implementé un solucionador de física para un juego 3d: podía optimizar el rendimiento drásticamente cambiando solo las cosas conectadas a donde se asignan los objetos y cómo se pasan los objetos en las funciones – Mat

+0

Usted sabe que la pila y el montón son esencialmente mismo bloque de memoria (almacenamiento en caché a un lado), la asignación es una cuestión de qué puntero se referencia como base. Lo sentimos, para que quede claro que el clásico stack/heap es esencialmente el mismo bloque de memoria – GrayWizardx

Respuesta

8

Mientras que en el caso general es cierto que los objetos siempre se asignan en el montón, C# le permite bajar al nivel del puntero para una interoperabilidad pesada o para un código crítico de muy alto rendimiento.

En unsafe bloques, puede usar stackalloc para asignar objetos en la pila y usarlos como punteros.

Para citar su ejemplo:

// cs_keyword_stackalloc.cs 
// compile with: /unsafe 
using System; 

class Test 
{ 
    public static unsafe void Main() 
    { 
     int* fib = stackalloc int[100]; 
     int* p = fib; 
     *p++ = *p++ = 1; 
     for (int i=2; i<100; ++i, ++p) 
     *p = p[-1] + p[-2]; 
     for (int i=0; i<10; ++i) 
     Console.WriteLine (fib[i]); 
    } 
} 

Nota sin embargo que no es necesario declarar un método completo insegura, que sólo puede utilizar un bloque unsafe {...} por ello.

+0

lo que estaba buscando =) ¡gracias! – Mat

+1

Tenga en cuenta que esto requiere algunos permisos especiales, por lo que si ejecuta su código en menos de un entorno de confianza completa, puede tener problemas. Un ejemplo de esto es Silverlight. – Blindy

+0

¿Quiso decir "en el caso general, es verdad que los objetos siempre se asignan en el * montón *"? –

6

Su explicación de dónde van los tipos de valores frente a los tipos de referencia (stack v. heap) no es completamente correcta.

Las estructuras también se pueden asignar en el montón si son miembros de un tipo de referencia, por ejemplo. O si los ha empaquetado al pasarlos a través de una referencia de objeto.

Debe leer http://www.yoda.arachsys.com/csharp/memory.html para obtener una mejor comprensión de dónde se asignan realmente los diferentes tipos.

En una nota aparte, en .Net, realmente no debería importar dónde se asigna un tipo, como escribe Eric Lippert: the stack is an implementation detail. Es mejor que comprendas la semántica de cómo se pasan los tipos (por valor, referencia, etc.).

Además, parece estar implicando que la asignación de un objeto en el montón es más costosa que en la pila. En realidad, yo diría que el costo de rendimiento de copiar los tipos de valor supera el beneficio de cualquier ahorro en una asignación ligeramente más rápida en la pila. La mayor diferencia entre la pila y el montón es que en la mayoría de las arquitecturas de CPU es más probable que la pila se conserve en la memoria caché de la CPU y, por lo tanto, evite fallas en la memoria caché.

Este no es el problema más importante para preocuparse por. Debe decidir si el tipo debe tener una semántica de valor por paso o no. Si no es así, entonces tal vez debería ser un tipo de referencia.

+0

digamos que se crea una matriz grande de algún tipo de objeto. La Matriz en sí irá al montón de todos modos. sin embargo, después de mi comprensión, si el tipo de objeto es una estructura, todo se asignará en una memoria contigua (un grupo de datos, o probablemente unos pocos divididos dependiendo de la implementación) en el montón. sin embargo, si se trata de una clase, cada elemento de la matriz se asignará también al montón, lo que lleva más tiempo para la asignación y hace que la recolección de basura sea más lenta – Mat

+0

@Mat: el hecho de que la matriz esté asignada en el montón es un detalle de implementación. Sin embargo, el hecho de que se trata de una serie de referencias (es decir, punteros, para nosotros, chicos de C++) no lo es. –

+0

@Pavel - exactamente eso es lo que quiero decir (no me importa si la matriz en sí misma está en el montón, ya que una sola asignación en el montón no afecta tanto como una asignación para cada elemento) - así que creo que es deseable para decidir el comportamiento del tipo de referencia/valor al momento del uso, y no al declarar la clase/estructura – Mat

1

No se preocupe, su cabeza todavía está en el mundo de c/C++ donde puede importar mucho donde va todo. Hay un grupo de personas realmente inteligentes en el equipo de CLR que pasan todo el día preocupándose por hacer esto mágicamente rápido.

Hay algunos aspectos críticos en C# y el uso de memoria por lo general assocaited con la creación de una gran cantidad de pequeños objetos por accidentes (que hace la cadena = cadena + otra cadena en un bucle es un clásico)

Hay una memprofiler que le mostrará qué está sucediendo si realmente cree que tiene un problema de Potencia causada por la administración de memoria

he escrito un montón de rendimiento del código intensa (de procesamiento de gráficos clientes, servidores de red) en C# y nunca tuvo que preocuparse por nada de este

+0

pero especialmente en la representación de gráficos, esta cifra marca una gran diferencia. por ejemplo, un sistema de partículas será mucho más lento si cada partícula (incluso cuando está almacenada en una matriz y reutilizada al morir) se asigna por sí misma en el montón a diferencia de estar asignada en un grupo de memoria – Mat

+0

hacer una gran variedad de partículas estructuras (como señalan otros carteles, puedes colocar estructuras por todos lados, no solo en la pila). Usted terminará con un gran bloque contiguo de memoria – pm100

+0

exactamente, pero el punto es que esta decisión debe tomarse cuando se escribe el código para la partícula. si desea utilizar alguna clase de una biblioteca, no tiene la opción de asignarla en un bloque contiguo – Mat

1

Esta es la forma incorrecta de ver las estructuras y las clases en C#. En C#, la diferencia entre una estructura y una clase no está donde se asigna, sino la semántica de la copia. Una estructura tiene semántica de valores y una clase tiene semántica de referencia. Los programadores de C++ tienden a leer más sobre esto, ya que están acostumbrados a que los objetos en la pila tengan semántica de valores y los objetos en el montón tengan semántica de referencia.

El modo en que se asigna esta memoria es un detalle de implementación del tiempo de ejecución. El tiempo de ejecución puede usar pilas, montones o cualquier otro esquema de asignación híbrido que quiera. Si bien es cierto que generalmente las estructuras se asignarán en algo así como una pila, y las clases se asignarán en una especie de montón, no es necesario. Por ejemplo, una clase asignada en una función y no pasada fuera del alcance de la función podría ser fácilmente asignada en la pila.

+0

tengo un control bastante bueno sobre la semántica de copia independientemente de si se trata de una estructura o clase. Puedo usar deepCopy/shallowCopy para copiar para tipos de referencia, puedo usar boxing/unboxing para obtener semántica de referencia para valuetype, puedo usar la palabra clave ref para aceptar tipos de valores por referencia como un parámetro de función. Pero decidir sobre dónde asignar una variable también puede ser importante para el rendimiento – Mat

+0

No estoy diciendo que elegir entre una clase y una estructura sea la única forma de controlar la semántica de copia. Estoy diciendo que class/struct solo trata sobre la semántica de copia, no sobre la política de asignación. Puede cambiar el rendimiento frente a un tiempo de ejecución particular persuadiéndolo para asignar de manera diferente, pero eso depende de la implementación. Lo cual está bien si está optimizando para el rendimiento. Sin embargo, los casos en los que esto realmente marcará una diferencia son probablemente más raros de lo que crees. – Niall

+0

pero esa era mi pregunta: mi problema es que tengo las herramientas para persuadir la semántica de copia, pero no tengo las herramientas para persuadir el comportamiento de asignación. Y esa clase/estructura no se trata de política de asignación no es verdadera, por ejemplo, lea este artículo msdn: http://msdn.microsoft.com/en-us/library/aa288471(VS.71).aspx "Cuando llame al Nuevo operador en una clase, se asignará en el montón. Sin embargo, cuando instancia una estructura, se crea en la pila. Esto generará mejoras en el rendimiento ". – Mat

2

No se deje engañar por la palabra clave new, es opcional para las estructuras.

En C# hay un mundo administrado en el que se disfruta del recolector de basura y Type-Safety y no tiene que preocuparse por los detalles de la memoria. La diferencia Stack/Heap es irrelevante, se trata de la semántica de copia.

Para los raros casos en los que desea control, existe la parte insegura (no administrada) de C# con punteros reales y todo.

Pero el costo de las cosas es diferente en C# que en C++, así que no cazar fantasmas, los objetos no administrados y de corta vida son muy baratos. Y el compilador puede asignar pequeñas matrices en la pila como una optimización, no podrá saber y tampoco debería importarle.

+0

pero hay una notable diferencia de rendimiento en el tiempo de asignación para estructuras y clases, por lo que sería bueno decidir dónde asignar en el momento de la asignación, y no al escribir la clase real – Mat

+0

¿Qué quiere decir con '' nuevo' ... es opcional para las estructuras "? ¿En que contexto? –

+0

Creo que quiere decir que para las estructuras, el constructor predeterminado se llama automáticamente, también sin la palabra clave new. sin embargo, para las clases, la variable será un puntero nulo hasta que se llame explícitamente a un constructor (usando nuevo) – Mat