2009-05-27 14 views
18

Por alguna razón, siempre he supuesto que los campos readonly tienen una sobrecarga asociada a ellos, lo cual pensé que era el CLR que hacía un seguimiento de si un campo readonly se había inicializado o no. La sobrecarga aquí sería algo de uso de memoria adicional para realizar un seguimiento del estado y una verificación al asignar un valor.¿Hay alguna sobrecarga en tiempo de ejecución para leer solo?

Quizás asumí esto porque no sabía que un campo readonly solo se podía inicializar dentro de un constructor o dentro de la propia declaración de campo y sin una verificación en tiempo de ejecución, no se podría garantizar que no se esté asignando a varias veces en varios métodos. Pero ahora que lo sé, podría ser comprobado de forma estática por el compilador de C#, ¿verdad? Entonces, ¿es ese el caso?

Otra razón es que he leído que el uso de readonly tiene un "leve" impacto en el rendimiento, pero nunca entraron en este reclamo y no puedo encontrar información sobre este tema, de ahí mi pregunta. No sé qué otro impacto en el rendimiento podría haber, aparte de los controles de tiempo de ejecución.

Una tercera razón es que he visto que readonly se conserva en la IL compilado como initonly, así que lo que es la razón de que esta información sea de la IL si readonly no es nada más que una garantía por el compilador de C# que el campo nunca se asigna a fuera de un constructor o declaración?

Por otro lado, he descubierto que puede establecer el valor de readonly int a través de la reflexión sin que el CLR arroje una excepción, lo que no debería ser posible si readonly fuera una verificación en tiempo de ejecución.

Así que mi suposición es: la 'readonlyness' es solo una característica de tiempo de compilación, ¿alguien puede confirmar/negar esto? Y si lo es, ¿cuál es el motivo de que esta información se incluya en la IL?

Respuesta

16

Tienes que mirarlo desde el mismo punto de vista que los modificadores de acceso. Los modificadores de acceso existen en IL, ¿pero realmente son un control en tiempo de ejecución? (1) No puedo asignar campos privados directamente en tiempo de compilación, (2) Puedo asignarlos usando reflexión. Hasta ahora parece que no hay verificación en tiempo de ejecución, como solo.

Pero examinemos los modificadores de acceso. Haga lo siguiente:

  1. Crear Asamblea A.dll con public class C
  2. Crear una b.exe Asamblea que hace referencia a A.dll. B.exe utiliza la clase C.
  3. Construya los dos conjuntos. Ejecutar B.exe funciona bien.
  4. Reconstruye A.dll pero establece la clase C en interna. Reemplaza A.dll en el directorio de B.exe.

Ahora, ejecutar B.exe arroja una excepción de tiempo de ejecución.

Existen modificadores de acceso en IL también, ¿verdad? Entonces, ¿cuál es su propósito? El propósito es que otros ensamblados que hacen referencia a un ensamblado .Net necesitan saber a qué tienen acceso y a qué no tienen acceso, tanto en tiempo de compilación como en tiempo de ejecución.

Readonly parece tener un propósito similar en IL. Le dice a otras asambleas si pueden escribir en un campo en un tipo particular. Sin embargo, readonlyno parece tener tener la misma comprobación en tiempo de ejecución que los modificadores de acceso exhiben en mi ejemplo anterior.Parece que readonly es una verificación en tiempo de compilación y no ocurre en tiempo de ejecución. Eche un vistazo a una muestra de rendimiento aquí: Read-only performance vs const.

Nuevamente, esto no significa que la IL es inútil. El IL se asegura de que se produzca un error en tiempo de compilación en primer lugar. Recuerde, cuando compila, no compila contra código, sino conjuntos.

+0

Vaya, usted encontró el mismo análisis de rendimiento que yo. Así que +1 para ti y he eliminado mi respuesta (menos detallada). – Eddie

+0

Tiene perfecto sentido, gracias :) – JulianR

6

Si está utilizando una variable de instancia estándar, readonly tendrá un rendimiento casi idéntico a una variable normal. El IL agregado se convierte en una verificación de tiempo de compilación, pero prácticamente se ignora en tiempo de ejecución.

Si está utilizando un miembro de sólo lectura estática, las cosas son un poco diferentes ...

Puesto que el elemento de sólo lectura estática se establece durante el constructor estático, el JIT "sabe" que existe un valor. No hay memoria extra, solo sirve para evitar que otros métodos lo configuren, pero eso es una verificación de tiempo de compilación.

Dado que el JIT sabe que este miembro nunca puede cambiar, se "codifica" en tiempo de ejecución, por lo que el efecto final es como tener un valor constante. La diferencia es que tomará más tiempo durante el tiempo de JIT en sí, ya que el compilador JIT necesita hacer un trabajo extra para cablear el valor del readonly en su lugar. (Esto va a ser muy rápido, sin embargo).

Expert C++/CLI por Marcus Hegee tiene una explicación bastante buena de esto.

3

Incluso si solo tiene efecto de lectura en tiempo de compilación, aún sería necesario almacenar los datos en el conjunto (es decir, el IL). El CLR es un ComúnIdioma Runtime: las clases escritas en un idioma pueden ser utilizadas y ampliadas por otros idiomas.

Como cada compilador del CLR no va a saber leer ni compilar ningún otro idioma, para preservar la semántica de los campos readonly, esos datos deben almacenarse en el ensamblado para que los compiladores de otros idiomas lo respetara

Por supuesto, el hecho de que el campo esté marcado readonly significa que el JIT puede hacer otras cosas, como optimización (por ejemplo, usos en línea del valor), etc. Independientemente del hecho de que haya utilizado la reflexión para cambiar el valor del campo, crear IL que modifica un campo initonly fuera del constructor correspondiente (instancia o estática, dependiendo del tipo de campo), dará como resultado un ensamblado no verificable.

4

Un punto importante aún no mencionado por ninguna otra respuesta es que cuando se accede a un campo de solo lectura, o cuando se accede a cualquier propiedad, la solicitud se satisface utilizando una copia de los datos. Si los datos en cuestión son un tipo de valor con más de 4-8 bytes de datos, el costo de esta copia adicional a veces puede ser significativo. Tenga en cuenta que si bien hay un gran aumento en el costo cuando las estructuras crecen de 16 bytes a 17, las estructuras pueden ser bastante más grandes y aún más rápidas que las clases en muchas aplicaciones, si no se copian con demasiada frecuencia. Por ejemplo, si se supone que uno tiene un tipo que representa los vértices de un triángulo en el espacio tridimensional. Una implementación directa sería una estructura que contiene una estructura con tres float para cada punto; probablemente 36 bytes en total. Si los puntos y las coordenadas dentro de cada punto son campos públicos mutables, se puede acceder al someTriangle.P1.X rápida y fácilmente, sin tener que copiar ningún dato excepto la coordenada Y del vértice 1. Por otro lado, si P1 era una propiedad o un readonly campo, el compilador tendría que copiar P1 en una estructura temporal, y luego leer X de eso.

+0

Esta puede ser una pregunta muy antigua, pero en caso de que cualquier lector futuro esté interesado, la siguiente entrada en el blog de Jon Skeet contiene información mucho más detallada relacionada con esta respuesta: http: //codeblog.jonskeet .uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/ –

+0

@KlitosKyriacou: No creo haber visto esa publicación en particular antes. Realmente desearía que .NET proporcionara un medio por el cual los métodos de estructura pudieran prometer no escribir 'esto' o explícitamente decir que lo hacen, ya que eso permitiría (1) mejorar el rendimiento en muchos casos cuando se invocan métodos que no lo hacen escriba 'this' [como se indica en el blog], y (2) permita que uno tenga métodos seguros que escriban' this' y haga que el compilador rechace los intentos de invocarlos en las no variables. Hay un número de casos en los que el tipo de datos más lógico sería semánticamente una estructura mutable, ya que ... – supercat

+0

... el patrón 'structVar = structVar.withSomeChange();' no se puede hacer seguro para subprocesos, pero 'structVar. makeSomeChange() 'puede [usar' Interlocked.CompareExchange']. Desafortunadamente, si una clase expone una propiedad de estructura [hipotética] tipo 'AtomicBitVector32', un compilador generará silenciosamente código falso para una operación como' thing.flags.TestAndSetBit (23); 'si' Flags' es una propiedad en lugar de una campo. – supercat

Cuestiones relacionadas