Performance o rendimiento, e implicaciones del uso de const o reandonly
Introducción
Hacía bastante tiempo que no escribía en el blog. La principal culpa de ello es un proyecto en el que estoy involucrado desde el primer día de Octubre y que me tiene felizmente ocupado.
No obstante, tenía muchas ganas de retomar mi blog aunque el tiempo libre que tengo sea un bien preciado, y era uno de mis objetivos en este nuevo año.
Durante todo este tiempo en el que he estado un poco apartado del blog, me han surgido muchas cosas algunas de las cuales me apetece contar, pero el tiempo es limitado y por lo tanto, no me permite contar todo, así que debo seleccionar alguna de ellas.
En este caso, voy a comentar lo que suele ser una pregunta general con respecto a const o readonly, y luego alguna implicación de su uso, ya que me encuentro a veces que se tienen dudas sobre ello.
Const o readonly
En primer lugar, la pregunta de si el uso de const o readonly tiene impacto en el rendimiento, o si es mejor utilizar una u otra.
La respuesta a esta pregunta es realmente simple, por eso la quiero abordar al principio del todo.
No. Así de simple.
No existe un impacto destacable en rendimiento entre const o readonly que justifique el uso de una u otra opción.
El uso declarativo de const se hace en tiempo de compilación, mientras que el readonly se hace en tiempo de ejecución.
A priori puede pensarse que const será más eficiente que readonly (y de hecho lo es), pero la realidad es que esa eficiencia no es por lo general algo destacable, y que en el caso de que así pudiera ser, estamos hablando de nanosegundos. Vamos… que no hay que hacerse pajas mentales y que si queremos mejorar el rendimiento de nuestras aplicaciones, es mejor centrarse en los bloques de código principales en lugar de intentar ganar un nanosegundo aquí.
Dicho y aclarado esto, vamos con la segunda de las preguntas que a mi juicio es mucho más importante que la primera pregunta.
¿Existe alguna implicación en usar const y readonly?.
Sí.
Y aquí me voy a detener más.
Antes comentaba que el uso de const se lleva a cabo en tiempo de compilación, y readonly en tiempo de ejecución.
Imaginemos entonces un ensamblado que posee dos variables, una constante (const) que por defecto es estátida, y una variable de sólo lectura (readonly) que podemos declarar como estática o no (esto no es relevante).
Algo así:
namespace FooAssembly
{
public class FooClass
{
public const string WithConst = «OneConst»;
public static readonly string WithReadOnly = «OneReadOnly»;
}
}
Compilamos el ensamblado. Todo perfecto.
Ahora creamos una aplicación de consola a la que agregamos como referencia nuestro ensamblado.
Y por supuesto, consumimos nuestro ensamblado.
Algo parecido a lo siguiente:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(«Demo const – readonly»);
Console.WriteLine(FooClass.WithConst);
Console.WriteLine(FooClass.WithReadOnly);
Console.ReadLine();
}
}
}
¿Qué información recibiremos por pantalla?.
Parece obvio que la pantalla nos mostrará la siguiente información:
OneConst
OneReadOnly
Y así sucede,…
Pero vayamos a reflector y analicemos el código del ensamblado de nuestra aplicación de consola.
Si abrimos nuestro ensamblado con Reflector, podremos ver algo que nos dará una pista directa de por donde voy a ir.
Observamos que en el caso del uso de la constante declarada en el primer ensamblado, ésta aparece «incrustada» en el ensamblado que la consume, ya que esta incrustación se ha realizado en tiempo de compilación.
Mientras que en el caso del uso de la variable de sólo lectura, se irá al ensamblado que la contiene para pedirle en tiempo de ejecución el valor de esa variable.
Con este escenario, planteemos ahora la siguiente ecuación.
Si el ensamblado que contiene la constante y la variable de sólo lectura, cambia,… es decir, algo como lo que voy a poner a continuación… ¿qué piensas que ocurrirá?.
namespace FooAssembly
{
public class FooClass
{
public const string WithConst = «TwoConst»;
public static readonly string WithReadOnly = «TwoReadOnly»;
}
}
En este caso, nuestra aplicación de consola mostrará algo parecido a lo siguiente:
OneConst
TwoReadOnly
El ensamblado de nuestra aplicación de consola sigue teniendo «incrustada» la constante que se definió en la primera versión del ensamblado que la contiene, mientras que en el caso de la variable de sólo lectura, como se va a buscar su valor en tiempo de ejecución, ésta sí se «actualiza» de acuerdo a la nueva versión del ensamblado.
¿Y como resolver el problema?.
Pues tenemos la opción de recompilar nuestra aplicación de consola que posee una referencia a nuestro ensamblado para que ésta «se entere» del nuevo valor de la constante y lo «incruste» correctamente.
En realidad no se va a enterar de nada, ya que lo único que hará será compilar el ensamblado e «incrustar» los valores de las constantes nuevamente, sin importarle los valores originales.
Lo que vemos aquí, es que indirectamente estamos acoplando nuestra aplicación de consola al ensamblado que contiene las constantes.
¿Y esto es bueno o es malo?.
Pues como siempre, depende de lo que queramos hacer.
Por una parte, puede parecer malo este acoplamiento, pero por otro lado, nos aseguramos (y pudiera interesarnos) que si alguien introduce una nueva versión del ensamblado que contiene estas constantes, el valor de las constantes en la aplicación permanecerá inalterable, por lo que forzaremos al cliente a obtener una nueva versión de (en este caso de ejemplo) la aplicación de consola.
Aún y así, la recomendación de diseño es que no se utilicen como constantes, valores o datos que presumiblemente podrían cambiar a lo largo del tiempo.
Un ejemplo práctico de lo que NO sería recomendado, podría ser el uso de una constante para indicar la versión del producto, o bien el porcentaje de descuento ante diferentes situaciones, o también una Uri a un recurso, etc… hay cientos de ejemplos, pero todos ellos dependerán de los requisitos de nuestra solución, de si pueden cambiar a lo largo del tiempo, etc.
Y ojo, esta misma problemática no sólo aplica a const, sino también a los argumentos con nombre y argumentos opcionales, que tan útiles son a veces y que tan peligrosos son sino los conocemos bien.
Y sobre la técnica empleada por Microsoft con respecto a esta forma de trabajar, su nombre es «constant folding«, y se emplea de esta forma por temas de rendimiento. Salvo que alguien me corrija, en Java funciona de la misma manera.
Por cierto… se me olvidaba.
Una variable declarada como const no puede ser modificada dentro de la clase en la que se declara.
Sin embargo, una variable declarada como readonly sí puede ser modificada dentro de la clase en la que se declara siempre y cuando no haya sido declarada como estática (como he hecho en el ejemplo). Esta modificación se debería hacer en el constructor. Esta posibilidad, nos puede ofrecer una flexibilidad que no ofrece una constante.
Es por esto, que en lugar de pensar en el rendimiento cuando nos preguntamos si usar una constante o una variable readonly, quizás deberíamos plantearnos otras preguntas y sus posibles implicaciones, como por ejemplo como el uso de una constante, podría «romper» nuestra aplicación, algo que ya me he preocupado yo de adelantarte en esta entrada.
Espero que te haya gustado y parecido entretenida e interesante la lectura de esta entrada. ¡Hasta la próxima!. 🙂