¿Cuántas veces nos hemos preguntado entre dos opciones cuál es más óptima? Hoy os muestro una utilidad que os puede ayudar.
Tabla de contenidos
Conociendo nuestro lenguaje
Lo primero que hay que saber para ver cómo se optimiza en el lenguaje que trabajamos es ver cómo funciona el mismo. En el caso de C# (java es similar), hay 2 fases principales:
Viendo el gráfico anterior, podemos apreciar como el compilar puede realizar optimizaciones tanto en la generación del código C#, como en la generación del IL(Intermediate Language) y en tiempo de ejecución, en la generación de las instrucciones que ejecutará nuestra CPU.
Sabiendo esto, estaría bien poder usar una herramienta que nos muestre tanto el código IL, como el ASM, resultado de la compilación de nuestro código fuente C#. Esta tarea la podemos hacer online con SharpLab.io
Métodos vs expression-bodied members
Una de las características de C# 6, son los expression-bodied members, lo que nos permite utilizar esta sintaxis:
public string GetName() => "Juan";
En lugar de:
public string GetName(){
return "Juan";
}
En el siguiente enlace podéis ver cómo se comporta el código anterior respecto a su rendimiento.
Rendimiento y código generado en modo debug
En modo debug, el IL resultante del método necesita ejecutar más instrucciones que en el caso del expression-bodied member. Además, a simple vista se puede ver que es más lento.
.method public hidebysig static
string GetName () cil managed
{
// Method begins at RVA 0x2050
// Code size 11 (0xb)
.maxstack 1
.locals init (
[0] string
)
IL_0000: nop
IL_0001: ldstr "Hola Juan"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method C::GetName
.method public hidebysig specialname static
string get_GetNameInline () cil managed
{
// Method begins at RVA 0x2067
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldstr "Hola Juan"
IL_0005: ret
} // end of method C::get_GetNameInline
En el caso del ASM, también ocurre lo mismo:
C.GetName()
L0000: push ebp
L0001: mov ebp, esp
L0003: push eax
L0004: cmp dword [0x17db1578], 0x0
L000b: jz L0012
L000d: call 0x73864330
L0012: xor edx, edx
L0014: mov [ebp-0x4], edx
L0017: nop
L0018: mov eax, [0xf1cf73c]
L001e: mov [ebp-0x4], eax
L0021: nop
L0022: jmp L0024
L0024: mov eax, [ebp-0x4]
L0027: mov esp, ebp
L0029: pop ebp
L002a: ret
C.get_GetNameInline()
L0000: push ebp
L0001: mov ebp, esp
L0003: cmp dword [0x17db1578], 0x0
L000a: jz L0011
L000c: call 0x73864330
L0011: mov eax, [0xf1cf73c]
L0017: pop ebp
L0018: ret
Rendimiento y código generado en modo release
Como todos sabréis, el rendimiento siempre se mide en modo release, que es donde el compilador aplica mayor número de optimizaciones. Ahora el IL es exactamente igual:
// Methods
.method public hidebysig static
string GetName () cil managed
{
// Method begins at RVA 0x2050
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldstr "Hola Juan"
IL_0005: ret
} // end of method C::GetName
.method public hidebysig specialname static
string get_GetNameInline () cil managed
{
// Method begins at RVA 0x2057
// Code size 6 (0x6)
.maxstack 8
IL_0000: ldstr "Hola Juan"
IL_0005: ret
} // end of method C::get_GetNameInline
En el caso del ASM, este es muy simple, carga el string ya generado en memoria a un registro y lo devuelve:
C.GetName()
L0000: mov eax, [0xf1cf73c]
L0006: ret
C.get_GetNameInline()
L0000: mov eax, [0xf1cf73c]
L0006: ret
Programación asíncrona
La programación asíncrona en C# permite delegar en el sistema operativo la gestión de las operaciones de entrada/salida bloqueantes (I/O), de manera que se pueden liberar los hilos que realizan estas operaciones, para ser reutilizados por otras partes del código. Esto permite grandes mejoras de rendimiento, como se puede ver en la siguiente infografía:
Conclusiones
Como se puede ver en el artículo:
- Hay mucha diferencia entre el código generado en modo Release y Debug, en producción siempre hay que usar el modo Release.
- Algunas características que se añaden a los lenguajes son solo syntax sugar
- Conociendo como funciona nuestro lenguaje podemos ver cómo optimiza el código resultante
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
Donald Knuth
Jorge Durán
Latest posts by Jorge Durán (see all)
- [Remix] Multitud de recursos para .NET - 18 junio, 2021
- ¿Qué es un puntero y cómo se usan? - 4 junio, 2021
- Los 10 mejores paquetes nuget que tienes que instalar - 26 abril, 2021