rss resume / curriculum vitae linkedin linkedin gitlab github twitter mastodon instagram
Notas de desempeí±o
Apr 25, 2006

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:

  1. Parar el uso de Enumeradores, reemplazalos con punteros, con cí³digo no administrado.
  2. No usar indexadores para acceder a arreglos cambiando el cí³digo a un uso de aritmí©tica de punteros, con cí³digo no administrado.
  3. Concatenaciones a traví©z de System.Text.StringBuilder, en vez de utilizar el clí¡sico "cadena + cadena".
  4. 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í­:

using System;

namespace Iterations
{
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:

repetitions: 1000
iterations: 1.000000e+006

Enumeration: 32.87 seconds
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:

using System;

namespace Research
{
unsafe public struct MyStruct
{
public MyStruct (int integer)
{
Integer = integer;
Next = null;
}

public int Integer;
public MyStruct *Next;

public override string ToString ()
{
return "Integer: "+Integer+" at "+
"Ptr "+((int)&(*Next));
}
}

//Quick sample, don't bother me ;-)
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);
}

unsafe public static void ChangeByPointer (int *reference)
{
*(reference) += 5;
}

unsafe public static void ChangeByReference (ref int reference)
{
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:

//... more before

// method line 5
.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 line 6
.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.


Back to posts