[Windows 10] Novedades y consejos sobre rendimiento

Branch-EngineeringIntroducción

Además de cuidar detalles como la funcionalidad o la apariencia visual de nuestra aplicación, nuestra aplicación debe funcionar correctamente bajo todas las condiciones en todos los dispositivos para la que sea lanzada.

Factores como el consumo de memoria, CPU o la gestión de tiempos, a veces cuestan y no se tienen en cuenta hasta que salta el problema, es decir, tarde.

¿Es importante el rendimiento?

Las críticas de la tienda así como la posibilidad de comunicación directa con los usuarios es una vía inmejorable para realizar una mejora continua de la misma. Las críticas habituales suelen venir en un alto porcentaje de errores (bugs) que no permiten realizar acciones contempladas en la aplicación y también muchas de ellas, por rendimiento.

 

Motivos de críticas negativas

Motivos de críticas negativas

Que la aplicación se “congele”, respuestas lentas, consumos muy elevados de batería, que provoquen un sobrecalentamiento del dispositivo son algunas causas habituales de opiniones como las siguientes:

Ejemplos de críticas negativas

Ejemplos de críticas negativas

Por lo tanto, debemos tener en cuenta en el propio desarrollo factores de rendimiento de igual forma que prestamos atención a la correcta visualización de la interfaz en distintas condiciones por ejemplo.

Mejoras de rendimiento en UWP

De entrada, buenas noticias. Se han incluido en Windows 10 mejoras como:

  • Rendimiento aumentado en Listview.
  • Mejoras en redimiento en ScrollViewer.
  • Interoperatibilidad XAML/DX.
  • Casting de elementos visuales.
  • Gestión Bitmap Source.
  • Etc.

Gracias a todas estas mejoras conseguimos sin cambios en nuestra forma de trabajar o en el código, mejoras en el consumo de CPU y de memoria.

Mejoras rendimiento UWP

Mejoras rendimiento UWP

Novedades en XAML

Con Windows 10 nos llegan un conjunto de novedades relacionados con la gestión de enlace a datos, nuevas etiquetas de marcado y otros detalles que afectan al rendimiento.

x:Bind

Data binding es un mecanismo mediante el cual podemos enlazar los elementos de la interfaz de usuario con los objetos que contienen la información a mostrar. Cuando realizamos data binding, creamos una dependencia entre el valor de una propiedad llamada target con el valor de otra propiedad llamada source. Donde normalmente, la propiedad target recibirá el valor de la propiedad source.

Es el mecanismo base que nos permite utilizar el patrón MVVM en nuestras Apps móviles logrando:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

Sin embargo, además de toda la potencia mencionada teníamos ciertas limitaciones. Los errores de Binding no se producían en tiempo de compilación de la App además de tener diferentes mejoras relacionadas con el rendimiento. Limitaciones existentes hasta ahora…

x:Bind es una nueva síntaxis en XAML que cubre un objetivo similar a Binding. Permite crear un enlace a datos pero con significativas diferencias. Mientras que con Binding se utiliza reflexión en tiempo de ejecución para resolver el enlace a datos, con x:Bind se realiza una validación en tiempo de ejecución ya que son fuertemente tipados y compilados. Además, ofrece potentes mejoras en el rendimiento.

Veamos unas simples comparativas entre enlace a datos clásico y precompilado y su impacto en el rendimiento.

Uso de CPU utilizando enlace a datos clásico:

Uso de CPU en Bindings clásicos

Uso de CPU en Bindings clásicos

Uso de CPU utilizando bindings compilados:

Uso de CPU en binding compilado

Uso de CPU en binding compilado

También se reduce el consumo de memoria en comparación con Bindings clásicos:

Comparativa de consumo de memoria entre Bindings

Comparativa de consumo de memoria entre Bindings

Vista las visibles mejoras a nivel de rendimiento, ¿cómo se usan?.

En esta ocasión, crearemos un listado de casas donde utilizaremos x:Bind en la plantilla que representará cada elemento de la lista.

Nuestra interfaz sera muy simple en esta ocasión contando con un sencillo ListView:

<ListView
     ItemsSource="{Binding Houses}" />

Cargaremos el listado de casas con un método creando datos falsos en local de manera aleatoria:

private void LoadHouses()
{
     _houses = new ObservableCollection<House>();
     Random random = new Random();
     for (int i = 0; i < 100; i++)
     {
          _houses.Add(new House
          {
               Place = Places[random.Next(0, Places.Count)],
               Photo = string.Format("ms-appx:///Assets/{0}.png", random.Next(1, 4)),
               Price = string.Format("${0}", random.Next(10000, 100000).ToString())
          });
     }
}

La definición del template de cada casa:

<DataTemplate x:Key="HouseTemplate" x:DataType="model:House">
     <Grid Width="200"
           Height="80">
          <Grid.ColumnDefinitions>
               <ColumnDefinition Width="75" />
               <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
               <RowDefinition Height="Auto" />
               <RowDefinition Height="Auto" />
           </Grid.RowDefinitions>
           <Image Grid.RowSpan="2"
              Source="{x:Bind Photo}"
              MaxWidth="70"
              MaxHeight="70" />
           <TextBlock Text="{x:Bind Place}"    
                  Grid.Column="1"
                  FontSize="18"/>
           <TextBlock Text="{x:Bind Price}"  
                  Grid.Column="1"  
                  Grid.Row="1"
                  FontSize="12" />
     </Grid>
</DataTemplate>

Utilizamos x:Bind para enlazar cada elemento visual de la plantilla a la propiedad deseada. Importante resaltar además de compilados, son fuertemente tipados. Es obligatorio para no tener errores de compilación indicar el tipo de los datos a los que accedemos por enlace a datos. Esto lo realizamos utilizando x:DataType.

Nuestro listado quedara:

<ListView
     ItemsSource="{Binding Houses}"
     ItemTemplate="{StaticResource HouseTemplate}" />
DataTemplate utilizando x:Bind

DataTemplate utilizando x:Bind

 

Lo visto hasta ahora nos indica que:
  • Tenemos la posibilidad de tener bindings compilados obteniendo errores en tiempo de compilación.
  • Son fuertemente tipados por lo que debemos indicar el tipo de la información.
  • Obtenemos mejoras en el rendimiento tanto en consumo de CPU como de memoria.

Por lo tanto, ¿lo usamos siempre?

La respuesta corta es no. Entrando en profundidad:

  • Los bindings compilados, en ocasiones,  tienen un comportamiento diferente al de los bindings clásicos existiendo situaciones no válidas para los primeros.
  • Los bindings compilados como indicamos al nombrarlos se compilan permitiéndonos obtener errores en tiempo de compilación pero tambien nos aporta limitaciones. No podemos crear bindings compilados dinámicamente (añadir o quitar bindings en runtime).
  • Con los bindings clásicos podemos crear un mismo template para entidades diferentes siempre y cuando el nombre de las propiedades coincida. Con los bindings compilados como hemos visto, estan fuertemente tipados y no podemos realizar lo mismo.

Podéis descargar el ejemplo utilizando x:Bind a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

x:Phase

Otra etiqueta nueva incluida en Windows 10, en este caso destinada a permitir realizar renderizado por fases. Con Windows 8.1 se introdujo en listados el evento ContainerContentChanging que nos permitía el renderizado progresivo de elementos. Requería código para actualizar la plantilla que dificultaba el uso de enlace a datos.

Ahora tenemos una nueva etiqueta llamada x:Phase que nos permite realizar con suma facilidad renderizado progresivo. Estableceremos valores numéricos.

NOTA: Por defecto el valor implícito es x:Phase=”0″.

<DataTemplate x:Key="XPhaseHouseTemplate" x:DataType="model:House">
     <Grid Width="200" 
           Height="80">
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="75" />
                 <ColumnDefinition Width="*" />
             </Grid.ColumnDefinitions>
             <Grid.RowDefinitions>
                 <RowDefinition Height="Auto" />
                 <RowDefinition Height="Auto" />
             </Grid.RowDefinitions>
             <Image 
                 Grid.RowSpan="2" 
                 Source="{x:Bind Photo}" 
                 x:Phase="2"
                 MaxWidth="70" 
                 MaxHeight="70" />
             <TextBlock 
                 Text="{x:Bind Place}"  
                 Grid.Column="1" 
                 FontSize="18"/>
             <TextBlock 
                 Text="{x:Bind Price}"  
                 x:Phase="1"
                 Grid.Column="1"  
                 Grid.Row="1"
                 FontSize="12" />
     </Grid>
</DataTemplate>

El uso de x:Phase esta asociado al uso de bindings compilados.

Ejemplo de x:Phase:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

x:DeferLoadStrategy

x:DeferLoadStrategy nos permite retrasar la creación de un elemento y sus elementos hijos lo que reduce los tiempos necesarios para la creación de la UI y por lo tanto de carga. Sin embargo, nada en la vida es gratis. A cambio, incrementamos levemente el consumo de memoria.

NOTA: Cada elemento que retrasamos en su inicialización con x:DeferloadStrategy añade 600 Bytes en el consumo de memoria.

Podemos deducir que a mayor cantidad de elementos que nos ahorremos del árbol visual, en menor tiempo se realizara la inicialización de la vista pero aumentando el consumo de memoria. Por lo tanto, el uso de la etiqueta es recomendado aunque requiere un análisis mínimo.

Creamos un ejemplo básico donde vamos a retrasar la creación de un Grid que contiene una imagen para crearlo bajo nuestro propio interés al pulsar el botón.

<Grid x:Name="DeferredPanel"  
      x:DeferLoadStrategy="Lazy">
      <Image
           Stretch="UniformToFill"
           Source="ms-appx:///Assets/NinjaCat.jpg" />
</Grid>

Utilizamos la etiqueta x:DeferLoadStrategy=”Lazy” en nuestro Grid. De esta forma indicamos que retrasamos la creación del panel y todo su contenido. Para utilizar la etiqueta debemos:

  • Definir un nombre con x:Name. Para iniciar posteriormente la incialización utilizaremos el nombre.
  • Podemos utilizarlo con cualquier elemento visual derivado de UIElement. No podemos utilizarlo con Page o UserControl.
  • No podremos utilizar con XAML XamlReader.Load.

Nos centramos ahora en el código que se ejecutará pulsando el botón:

page.FindName("DeferredPanel");

Utilizamos el método FindName pasándole el nombre del elemento.

Al pulsar el botón, el panel que retrasamos se crea. En este momento:

  • Se lanza el evento Loaded del panel.
  • Se evalúan los Bindings establecidos en el elemento.

Utilizando x:DeferLoadStrategy

Ejemplo de x:DeferLoadStrategy:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Otras consideraciones

Virtualización

Tanto el control ListView como el control GridView soportan nativamente virtualización. Sin embargo, hay varios modos para perder la virtualización, debemos tener en cuenta:

  • Si envolvemos un control ListView o GridView sobre un ScrollViewer, su tamaño tiende a infinito, sin establecer límites perderíamos la virtualización.
  • Podemos organizar los elementos utilizando diferentes paneles. Paneles como por ejemplo WrapGrid no soporta virtualización.

Optimiza imágenes

Las imágenes de tamaños muy elevados, sobretodo si las vamos a mostrar en tamaños mucho mas reducidos, no compensan directamente debido a la enorme cantidad de memoria que consumen.

Se pueden utilizar las propiedades DecodePixelHeight y DecodePixelWidth de un BitmapImage para establecer el alto y ancho en el que se decodifica la imagen.

BitmapImage bi = new BitmapImage(new Uri(baseUri, path));
bi.DecodePixelHeight = 120;
bi.DecodePixelWidth = 180;

Ejemplo:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Optimiza textos

El renderizado de texto es en ocasiones hasta un 50% más rápido en Windows 10. Podemos aumentar el rendimiento usando:

  • CharacterSpacing
  • Typography
  • LineStackingStregy=BaselineToBaseline/MaxHeight
  • IsTextSelectionEnabled = true

Herramientas

Cada vez que nos llega a los desarrolladores un nuevo SDK, es un momento especial con una mezcla de altísima curiosidad y ganas de probar novedades. Entre las novedades principales siempre hay nuevas APIs, controles y otros elementos para poder realizar Apps que antes no eran posibles. Sin embargo, entre el conjunto de novedades siempre suelen venir nuevas herramientas que facilitan ciertas tareas: obtener más analíticas, mejores medidores de rendimiento, más opciones en emuladores, etc.

Visual Tree Inspector

Desde versiones anteriores de Visual Studio, una de las herramientas más demandadas son herramientas de depuración de UI XAML.

El árbol visual dinámico es la primera de dos piezas fundamentales para depurar UI XAML.

Visual Tree Inspector

Esta herramienta nos permite ver el árbol de controles de la App en ejecución indicando el número de elementos hijos de cada elemento ideal para entender la estructura visual de una vista compleja y entender problemas de rendimiento.

La segunda pieza relacionada con las herramientas de depuración de UI XAML es el explorador de propiedades dinámico.

Live Property Explorer

Esta herramienta nos permite ver todas las propiedades del elemento seleccionado, incluso aquellas sobreescritas. Podemos ver si las propiedades estan establecidas con valores directos, accediendo a recursos, etc. Además, y la parte más interesante, permite cambiar los valores de la App en ejecución directamente viendo los cambios de manera inmediata.

PerfTips

Generalmente y a pesar de contar con herramientas de diagnóstico, no se suelen utilizar hasta que surgen problemas, es decir, tarde. En estos casos, una vez detectados problemas de rendimiento, además de utilizar las herramientas de diagnóstico se suelen poner puntos de ruptura entre diferentes bloques para tener una idea de donde se pierde tiempo.

Estas prácticas no suelen ser muy buena idea. Por un lado se “caza” al problema cuando ya es un problema grande, y por otro lado, con puntos de ruptura o añadiendo líneas para obtener tiempos entre dos puntos del código, no suele ser muy exacto.

PerfTips llega para ayudar a a entender que ocurre en su aplicación a nivel de rendimiento mientras depura. En los puntos de ruptura aparecerán popups con información relacionada con el rendimiento.

PerfTips

PerfTips

PerfTips indica tiempos aproximados excluyendo los tiempos de pausa en un punto de ruptura así como la carga de símbolos y tiempo correspondiente al debugger.

Herramientas de diagnóstico

Las herramientas de diagnóstico son un conjunto de ventanas destinadas a ofrecer información relacionada con el rendimiento de la aplicación. Tenemos opciones para ver problemas de renderizado y parsing en la aplicación, monitorear consumo de Memoria y CPU o detectar problemas en el consumo de red entre otras opciones.

Consumo de CPU

Muestra el uso de CPU en todos los cores disponibles:

Consumo de CPU

Consumo de CPU

Consumo de memoria

Monitorea el consumo de memoria de la aplicación mientras estamos depurando.

Consumo de Memoria

Consumo de Memoria

Ahora disponible siempre tras cada sesión de depuración.

Renderizado y parsing

Identifica problemas de rendimiento relacionados con:

  • Parsing & Layout
  • Código de la App que provoca consume alto de CPU
Línea de tiempo

Línea de tiempo

Más información

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s