Rendimiento para el modelado de clases

Rendimiento. En muchos proyectos en los que trabajo una de las preocupaciones a la hora de hacer el proyecto es el rendimiento de la aplicación

Una de las tareas, por no decir la única, es trabajar con datos en una aplicación, modelamos constantemente clases que tiene estado y a su vez exponen una serie de métodos para que los podamos invocar. Hoy a lo que me voy a dedicar a explicar es justamente a ese modelado de datos, al estado de nuestras clases.

Dentro de .NET Framework tenemos varias maneras de definir el estado de una clase:

  • Field (Campo)
  • Property (Propiedad)
  • DependencyProperty (Propiedades de Dependencia)

Estas últimas están pensadas para la interacción de esas clases con Interfaz de Usuario (UI).

Las propiedades son una normalización de los métodos de acceso (set) o de obtención (get) que normalmente se hacía para obtener el acceso a los campos o modificarlos. Realmente las variables se guardan en los campos (field) solo que las propiedades permiten normalizar el acceso y encapsular su funcionalidad de acceso. Son implementadas como métodos dentro del ensamblado.

Lo que vamos a ver en este post es cual la velocidad de acceso a estas propiedades desde .net para saber cuál es la mejor opción si estamos trabajando con el estado de las propiedades y campos. Cualquiera me podría decir que según la lista anterior está claro que siempre la mejor opción (desde el punto de vista del rendimiento) son los campos (Field) puesto que accederos directamente y sin intermediarios, pero además de eso cuando los proyectos empiezan a complicarse puede pensar en usar la reflexión para acceder a los datos, porque se quiere automatizar el acceso a las propiedades de las clases.

Así que lo que vamos a hacer es tener dos tipos de clases una con campos y propiedades, (Field y Property), otra con DependencyProperty y vamos a medir el tiempo que tardamos en acceder a esas variables. Una de las desventajas de usar DP es que para poder usar su funcionalidad tenemos que heredar de la clase DependencyObject, que es la clase que implementa la funcionalidad de DP. Además como he comentado anteriormente las DP únicamente se usar para definir las propiedades de los controles de interfaz de usuario y de los objetos de negocio que interactúan con la UI.

Lo que vamos a hacer son una serie de pruebas con una clase intermedia para medir el tiempo y que se encargue de ejecutar el bloque de código n veces y de medir el tiempo que tarda en hacerlo.

Para la medida del tiempo no vamos a usar DateTime.Now antes de empezar y después de ejecutar porque la medida de tiempo de DateTime.Now no es de alta resolución y no vamos a tener la precisión que necesitamos para medir diferencias de tiempo. En vez de DateTime.Now vamos a usar Stopwatch, clase que está en System.Diagnosis que nos permite hacer medidas de tiempo en alta resolución, el uso de esta clase es bastante sencilla así que el lector la podrá descubrir por el mismo.

Vamos al lio, tenemos está clase normal de C#

public class NormalType
{
    internal int number;

    public int Number
    {
        get { return number; }
        set { number = value; }
    }

    internal string _string;

    public string String
    {
        get { return _string; }
        set { _string = value; }
    }
    internal Item item;

    public Item Item
    {
        get { return item; }
        set { item = value; }
    }
}

Como podéis ver es una clase con tres propiedades un entero, un string y un tipo referencia complejo (Item), todas las propiedades tienen un campo que respalda el valor, así que realmente las propiedades simplemente exponen los valores de los campos sin más.

public class DependencyPropertyType : DependencyObject
{
    public int Number
    {
        get { return (int)GetValue(NumberProperty); }
        set { SetValue(NumberProperty, value); }
    }

    public static readonly DependencyProperty NumberProperty =
        DependencyProperty.Register("Number", typeof(int), typeof(DependencyPropertyType), new PropertyMetadata(0));

    public string String
    {
        get { return (string)GetValue(StringProperty); }
        set { SetValue(StringProperty, value); }
    }

    public static readonly DependencyProperty StringProperty =
        DependencyProperty.Register("String", typeof(string), typeof(DependencyPropertyType), new PropertyMetadata(null));

    public Item Item
    {
        get { return (Item)GetValue(ItemProperty); }
        set { SetValue(ItemProperty, value); }
    }

    public static readonly DependencyProperty ItemProperty =
        DependencyProperty.Register("Item", typeof(Item), typeof(DependencyPropertyType), new PropertyMetadata(null));

}

Esta otra clase es la clase que implementa las DependencyProperty, como veis ahora por cada propiedad hay una definición de una clase de tipo DependencyProperty que es estático y de solo lectura (static readonly) y además no es el tipo que definimos las propiedades. Eso que significa que los valores de esta clase son compartidos, desde luego no porque las propiedades son de instancia no estáticas, solo que ahora utilizamos dos métodos helpers que nos ayudan a acceder y establecer los valores de las propiedades (GetValue,SetValue). Esos dos métodos están definidos en la clase base DependencyObject. Realmente nosotros no tenemos información de donde se están guardando esas variables dentro de nuestra clase no hay ningún campo (Field) ni lista ni nada que nos indique donde se están almacenando esos valores, y si miramos en la clase base nos pasa lo mismo. En otro post desvelare toda la potencia y misterios de las DP ahora simplemente lo usaremos sin más para esta prueba de rendimiento.

Ahora lo que vamos a hacer son las pruebas en sí de acceso a estos valores.

image 

Sin reflexion

Con Reflexion

Getter Field

92,176

4483,588

Getter Property

142,813

3169,277

Getter Dependency Property

755,433

1619,854

Setter Field

0,607

3716,121

Setter Property

0,584

2600,364

Setter Dependency Property

129,623

973,799

La prueba está dividida en dos grandes grupos Setters y Getters (establecer y obtener).

Para los Setters (establecer) los campos tiene el mejor rendimiento cuando se trabaja con ellos, pero son los que tiene el peor rendimiento cuando se trata de acceder a través de reflexión, las propiedades es el mismo caso solo que se mejora un poco el tiempo de acceso desde reflexión y para las Dependency Property están muy equilibradas en cuanto a tiempo de acceso con y sin reflexión.

En el caso de Getters (obtener) la historia se repite, los campos y propiedades son increíblemente rápidos, pero cuando se trabaja con ellos a través de reflexión se penaliza, las Dependency Property en relación con el Setter son más rápidas con y sin reflexión.

El caso especial de las Dependency Proeprty.

¿Por qué las Dependency Properties tiene mejores tiempos?, podríamos decir que la prueba esta adulterada, porque realmente siempre se utiliza los métodos GetValue y SetValue que DependencyObject nos proporciona para acceder a esos valores y ahí es donde está la sobrecarga pero hay que tener en cuenta que la reflexión usada en esta prueba es solo para acceder a las instancias de las DependencyProperty que son necesarias en las llamadas de GetValue y SetValue.

Por si sentís curiosidad los valores de las instancias de clases que heredan de DependencyObject se guardan en un campo interno de la clase DependencyObject llamado _effectiveValues de tipo EffectiveValueEntry,

private EffectiveValueEntry[] _effectiveValues;

EffectiveValueEntry es la clase que manera el valor de una Dependency Property, cada una de estos EffectiveValueEntry tiene asociado un índice dentro de todos los registros de Dependency Proeprty al cual nosotros podemos acceder desde DependencyProperty.GlobalIndex (int) pero no podemos usar de ninguna manera, no sé porque Microsoft ha permitido acceder a esta propiedad si no tiene utilizad.

La solución de Visual Studio para que hagáis vuestras pruebas.

http://www.luisguerrero.net/downloads/DPPerformance.zip

Luis Guerrero.

Published 2/6/2009 1:27 por Luis Guerrero
Archivado en:
Comparte este post:

Comentarios

# re: Rendimiento para el modelado de clases

Wednesday, June 3, 2009 6:34 PM por Carlos Segura

Es curioso, no lo conocía, he usado las propiedades de dependencia solo en un par de ocasiones y por que el API con el que trabajaba las usaba, de todas formas no logro entender como apesar de hacer cast (que es una operación que lleva más tiempo), es más rápido. Tengo que ver el IL.

Un saludo,

Carlos.