A pesar de todos los comentarios puristas, usualmente absurdos y sin fundamentos, referentes a la plataforma Mono (y MS .NET), escribir cí³digo en esa plataforma realmente agiliza el desarrollo, en .NET no todo es "arrastrar y soltar", ni llenar las "formas" y cambiarles los colores arbitrariamente, ni hacer supuesta orientacií³n a objetos copiando trozos de cí³digo (estí¡ticos por cierto) a diferentes mí©todos. Escribir cí³digo en .NET cualquiera lo puede hacer, pero el hacerlo bien requiere habilidades y experiencia en otros lenguajes.
Podríamos pensar para escribir una aplicacií³n de tiempo real debemos seleccionar un lenguaje que pueda al final general un binario dependiente de plataforma y arquitectura, de esta forma se podrí¡n explotar, entre otras cosas, las capacidades de CPU y un manejo adecuado de memoria y administracií³n de esta, ¿Quí© es lo malo?, hay que saber como hacerlo y aprender lleva tiempo y tiempo es dinero, mí¡s tiempo significa mí¡s inversií³n y eso es algo de lo que usualmente no disponemos, ademí¡s la plataforma que se ofrece a traví©s de la conjuncií³n del CLR y el CIL hacen que podamos tener lo mejor de los dos mundos, hacer que el tiempo de desarrollo se reduzca y el desempeí±o sea al menos semejante a una aplicacií³n de arquitectura y plataforma dependiente, es obvio que un desempeí±o excelente en comparacií³n a un compilado-dependiente no se podrí¡ obtener, pero sin embargo siempre se busca el mejor.
Mi proyecto actual del trabajo es el claro ejemplo del extremo, donde se debe escribir una aplicacií³n en .NET con un desempeí±o excelente, el retardo mí¡ximo de actualizacií³n es de 3 segundos, actualmente lo he bajado de 6-8 a 3-1 segundos, cosa que me agrada, pero seguro se puede mejorar mí¡s, algunas cosas que me ayudaron a mejorar el desempeí±o fue:
- Parar el uso de Enumeradores, reemplazalos con punteros, con cí³digo no administrado.
- No usar indexadores para acceder a arreglos cambiando el cí³digo a un uso de aritmí©tica de punteros, con cí³digo no administrado.
- Concatenaciones a traví©z de System.Text.StringBuilder, en vez de utilizar el clí¡sico "cadena + cadena".
- Dí©bido a que recibo informacií³n binaria de estructuras escritas en C mediante un broadcast, es necesario generar las versiones C# correspondientes a aquellas struct en C con un Marshalling de modo que se pueda hacer un casting de tipo *(Estructura *) ptr.
Existe un artículo que muestra un buen caso de ejemplo indicando desempeí±o al momento de hacer ciclos a arreglos, dentro se utiliza un ejemplo de punteros escritos en C++ administrado, la versií³n de C# sería algo así:
namespace Iterationsusing System;
{
public class Pointer
{
public unsafe static void iterate (Data data)
{
double d;
fixed (double *ptr = &data.Array [0]) {
int l = data.Array.Length;
for (int i = 0; i < l; i++)
d = *(ptr +i);
}
}
}
}
Los resultados son contundentes:
Enumeration: 32.87 secondsrepetitions: 1000
iterations: 1.000000e+006
Indexing: 11.246 seconds
Indirect Arrays: 10.172 seconds
Direct Arrays: 5.44 seconds
Pointer Math: 4.828 seconds
El retardo principal se debe a los objetos generados durante la enumeracií³n, foreach no es la eleccií³n en aplicaciones de alto desempeí±o, sin duda es sencillo de implementar pero es lento al ejecutar, la solucií³n mí¡s rí¡pida a traví©z de punteros se debe a que validaciones como el índice del arreglo no es considerado, dejando todo la lí³gica de validacií³n al programador.
Hay cosas que me faltan de eliminar como ese abuso exagerado de boxing/unboxing al momento de tener mi coleccií³n de estructuras recibidas por el broadcast, ademí¡s de la creacií³n innecesario de tipos por valor, reemplazando con una lista enlazada en cí³digo no administrado y pasos por referencia respectivamente. Un ejemplo de esto sería:
namespace Research public int Integer; public override string ToString () //Quick sample, don't bother me ;-) unsafe public static void ChangeByPointer (int *reference) unsafe public static void ChangeByReference (ref int reference)using System;
{
unsafe public struct MyStruct
{
public MyStruct (int integer)
{
Integer = integer;
Next = null;
}
public MyStruct *Next;
{
return "Integer: "+Integer+" at "+
"Ptr "+((int)&(*Next));
}
}
public class Sample
{
unsafe public static void Main (string []args)
{
MyStruct obj1 = new MyStruct ();
MyStruct obj2 = new MyStruct ();
obj1.Next = null;
obj2.Next = &obj1;
obj1.Integer = 1;
obj2.Integer = 2;
ChangeByPointer (&obj1.Integer);
ChangeByReference (ref obj2.Integer);
Console.WriteLine (obj1 +" * "+ obj2);
}
{
*(reference) += 5;
}
{
reference += 5;
}
}
}
Algo interesante es el hecho de que ambos mí©todos generan la misma secuencia de instrucciones CIL, lo cual obviamente indica que son iguales, por tal razí³n cualquier eleccií³n es buena:
// method line 5 // method line 6//... more before
.method public static hidebysig
default void ChangeByPointer (int32* reference) cil managed
{
// Method begins at RVA 0x21c4
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: dup
IL_0002: ldind.i4
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stind.i4
IL_0006: ret
} // end of method Sample::default void ChangeByPointer (int32* reference)
.method public static hidebysig
default void ChangeByReference (int32& reference) cil managed
{
// Method begins at RVA 0x21cc
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: dup
IL_0002: ldind.i4
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stind.i4
IL_0006: ret
} // end of method Sample::default void ChangeByReference (int32& reference)
//more later...
Faltan detalles por mejorar, pero sin duda "jugar" con punteros siempre serí¡ lo mejor.