Compartir a través de


Optimización del marcado XAML

El análisis del marcado XAML para construir objetos en memoria consume mucho tiempo para una interfaz de usuario compleja. Estas son algunas cosas que puedes hacer para mejorar el tiempo de análisis y carga del marcado XAML y la eficacia de memoria de la aplicación.

En el inicio de la aplicación, limite el marcado XAML que se carga solo a lo que necesita para la interfaz de usuario inicial. Examine el marcado en la página inicial (incluidos los recursos de página) y confirme que no está cargando elementos adicionales que no son necesarios inmediatamente. Estos elementos pueden provenir de una variedad de orígenes, como diccionarios de recursos, elementos que se contraen inicialmente y elementos dibujados sobre otros elementos.

La optimización de XAML para la eficiencia requiere hacer concesiones; no siempre hay una sola solución para cada situación. Aquí se examinan algunos problemas comunes y se proporcionan instrucciones que puedes usar para hacer las ventajas y desventajas adecuadas para tu aplicación.

Minimizar el recuento de elementos

Aunque la plataforma XAML es capaz de mostrar un gran número de elementos, puedes hacer que la aplicación se publique y represente más rápido mediante el menor número de elementos para lograr los objetos visuales que quieras.

Las opciones que tome en la forma en que se muestran los controles de interfaz de usuario afectarán al número de elementos de interfaz de usuario que se crean cuando se inicia la aplicación. Para obtener información más detallada sobre cómo optimizar el diseño, consulta Optimizar el diseño XAML.

El recuento de elementos es extremadamente importante en las plantillas de datos porque cada elemento se crea de nuevo para cada elemento de datos. Para obtener información sobre cómo reducir el número de elementos en una lista o cuadrícula, consulta Reducción de elementos por elemento en el artículo de optimización de la interfaz de usuario de ListView y GridView.

Aquí, veremos otras formas de reducir el número de elementos que la aplicación tiene que cargar al iniciarse.

Aplazar la creación de elementos

Si el marcado XAML contiene elementos que no se muestran inmediatamente, puedes aplazar la carga de esos elementos hasta que se muestren. Por ejemplo, puede retrasar la creación de contenido no visible, como una pestaña secundaria en una interfaz de usuario similar a pestañas. O bien, puede mostrar elementos en una vista de cuadrícula de forma predeterminada, pero proporcionar una opción para que el usuario vea los datos en una lista en su lugar. Puede retrasar la carga de la lista hasta que sea necesario.

Use el atributo x:Load en lugar de la propiedad Visibility para controlar cuándo se muestra un elemento. Cuando la visibilidad de un componente se establece en Collapsed, se salta durante el paso de representación, pero todavía se incurren en los costos de la instancia de objeto en la memoria. Cuando se usa x:Load en su lugar, el marco no crea la instancia del objeto hasta que sea necesario, por lo que los costos de memoria son incluso menores. El inconveniente es que se paga una sobrecarga de memoria pequeña (aprox. 600 bytes) cuando la interfaz de usuario no se carga.

Nota:

Puede retrasar la carga de elementos mediante el atributo x:Load o x:DeferLoadStrategy. El atributo x:Load está disponible a partir de Windows 10 Creators Update (versión 1703, compilación 15063 del SDK). La versión mínima dirigida por el proyecto de Visual Studio debe ser Windows 10 Creators Update (10.0, compilación 15063) con el fin de usar x:Load. Para tener como destino versiones anteriores, use x:DeferLoadStrategy.

En los ejemplos siguientes se muestra la diferencia en el recuento de elementos y el uso de memoria cuando se usan diferentes técnicas para ocultar elementos de la interfaz de usuario. ListView y GridView que contienen elementos idénticos se colocan en la cuadrícula raíz de una página. ListView no está visible, pero se muestra GridView. El CÓDIGO XAML de cada uno de estos ejemplos genera la misma interfaz de usuario en la pantalla. Usamos las herramientas de de Visual Studio para generar perfiles y rendimiento para comprobar el número de elementos y el uso de memoria.

Opción 1: ineficaz

Aquí, se carga ListView, pero no está visible porque es Width 0. ListView y cada uno de sus elementos secundarios se crean en el árbol visual y se cargan en la memoria.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE.-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <ListView x:Name="List1" Width="0">
        <ListViewItem>Item 1</ListViewItem>
        <ListViewItem>Item 2</ListViewItem>
        <ListViewItem>Item 3</ListViewItem>
        <ListViewItem>Item 4</ListViewItem>
        <ListViewItem>Item 5</ListViewItem>
        <ListViewItem>Item 6</ListViewItem>
        <ListViewItem>Item 7</ListViewItem>
        <ListViewItem>Item 8</ListViewItem>
        <ListViewItem>Item 9</ListViewItem>
        <ListViewItem>Item 10</ListViewItem>
    </ListView>

    <GridView x:Name="Grid1">
        <GridViewItem>Item 1</GridViewItem>
        <GridViewItem>Item 2</GridViewItem>
        <GridViewItem>Item 3</GridViewItem>
        <GridViewItem>Item 4</GridViewItem>
        <GridViewItem>Item 5</GridViewItem>
        <GridViewItem>Item 6</GridViewItem>
        <GridViewItem>Item 7</GridViewItem>
        <GridViewItem>Item 8</GridViewItem>
        <GridViewItem>Item 9</GridViewItem>
        <GridViewItem>Item 10</GridViewItem>
    </GridView>
</Grid>

Árbol visual dinámico con listView cargado. El número total de elementos de la página es 89.

Captura de pantalla del árbol visual con vista de lista.

ListView y sus elementos secundarios se cargan en la memoria.

Captura de pantalla de la tabla Managed Memory Test App 1.EXE que muestra ListView y sus elementos secundarios están cargados en la memoria.

Opción 2: mejor

Aquí, la visibilidad de ListView se establece en contraído (el otro XAML es idéntico al original). ListView se crea en el árbol visual, pero sus elementos secundarios no. Sin embargo, se cargan en memoria, por lo que el uso de memoria es idéntico al ejemplo anterior.

<ListView x:Name="List1" Visibility="Collapsed">

Árbol visual dinámico con listView contraído. El número total de elementos de la página es 46.

Captura de pantalla del árbol visual con vista de lista colapsada.

ListView y sus elementos secundarios se cargan en la memoria.

Una captura de pantalla actualizada de la tabla Managed Memory Test App 1 dot E X E que muestra que ListView y sus elementos secundarios se cargan en la memoria.

Opción 3: más eficaz

Aquí, listView tiene el atributo x:Load establecido en False (el otro XAML es idéntico al original). ListView no se crea en el árbol visual ni se carga en la memoria al iniciarse.

<ListView x:Name="List1" Visibility="Collapsed" x:Load="False">

Árbol visual dinámico con listView no cargado. El número total de elementos de la página es 45.

Árbol visual con vista de lista no cargada

ListView y sus elementos secundarios no se cargan en la memoria.

árbol visual con vista de lista

Nota:

Los recuentos de elementos y el uso de memoria en estos ejemplos son muy pequeños y solo se muestran para demostrar el concepto. En estos ejemplos, la sobrecarga de usar x:Load es mayor que el ahorro de memoria, por lo que la aplicación no se beneficiaría. Debes usar las herramientas de generación de perfiles de la aplicación para determinar si la aplicación se beneficiará o no de la carga diferida.

Usar propiedades del panel de diseño

Los paneles de diseño tienen una propiedad Fondo, por lo que no es necesario colocar un rectángulo delante de un Panel solo para colorearlo.

ineficaz

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid>
    <Rectangle Fill="Black"/>
</Grid>

eficiente

<Grid Background="Black"/>

Los paneles de diseño también tienen propiedades de borde integradas, por lo que no es necesario colocar un elemento Border alrededor de un panel de diseño. Consulta Optimizar tu diseño XAML para obtener más información y ejemplos.

Uso de imágenes en lugar de elementos basados en vectores

Si reutiliza el mismo elemento basado en vectores suficientes veces, resulta más eficaz usar un elemento Image en su lugar. Los elementos basados en vectores pueden ser más caros porque la CPU debe crear cada elemento individual por separado. El archivo de imagen solo debe descodificarse una vez.

Optimización de recursos y diccionarios de recursos

Normalmente, usas diccionarios de recursos para almacenar, en un nivel algo global, los recursos a los que quieres hacer referencia en varios lugares de la aplicación. Por ejemplo, estilos, pinceles, plantillas, etc.

En general, hemos optimizado ResourceDictionary para no crear instancias de recursos hasta que se les solicite. Pero hay situaciones que debe evitar para que los recursos no se instancien innecesariamente.

Recursos con x:Name

Use el atributo x:Key para hacer referencia a los recursos. Cualquier recurso con el atributo x:Name no se beneficiará de la optimización de la plataforma; en su lugar, se instancia tan pronto como se crea ResourceDictionary. Esto sucede porque x:Name indica a la plataforma que la aplicación necesita acceso de campo a este recurso, por lo que la plataforma debe crear algo para crear una referencia.

ResourceDictionary en un Control de Usuario

Un ResourceDictionary definido dentro de un UserControl conlleva una penalización. La plataforma crea una copia de cada ResourceDictionary para cada instancia del UserControl. Si tiene un UserControl que se usa mucho, mueva ResourceDictionary fuera de UserControl y colóquelo en el nivel de página.

Ámbito de Recurso y Diccionario de Recursos

Si una página hace referencia a un control de usuario o a un recurso definido en un archivo diferente, el marco también analiza ese archivo.

Aquí, dado que InitialPage.xaml usa un recurso de ExampleResourceDictionary.xaml, el ExampleResourceDictionary.xaml completo debe analizarse al iniciarse.

InitialPage.xaml.

<Page x:Class="ExampleNamespace.InitialPage" ...>
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ExampleResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <Grid>
        <TextBox Foreground="{StaticResource TextBrush}"/>
    </Grid>
</Page>

ExampleResourceDictionary.xaml.

<ResourceDictionary>
    <SolidColorBrush x:Key="TextBrush" Color="#FF3F42CC"/>

    <!--This ResourceDictionary contains many other resources that
        are used in the app, but are not needed during startup.-->
</ResourceDictionary>

Si usas un recurso en muchas páginas de la aplicación, almacenarlo en app.xaml es un procedimiento recomendado y evita la duplicación. Pero App.xaml se analiza en el inicio de la aplicación, por lo que cualquier recurso que se use en una sola página (a menos que esa página sea la página inicial) se coloque en los recursos locales de la página. En este ejemplo se muestra App.xaml que contiene recursos que son utilizados por una sola página (no es la página inicial). Esto aumenta innecesariamente el tiempo de inicio de la aplicación.

App.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Application ...>
     <Application.Resources>
        <SolidColorBrush x:Key="DefaultAppTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="InitialPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="SecondPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="ThirdPageTextBrush" Color="#FF3F42CC"/>
    </Application.Resources>
</Application>

InitialPage.xaml.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.InitialPage" ...>
    <StackPanel>
        <TextBox Foreground="{StaticResource InitialPageTextBrush}"/>
    </StackPanel>
</Page>

SecondPage.xaml.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.SecondPage" ...>
    <StackPanel>
        <Button Content="Submit" Foreground="{StaticResource SecondPageTextBrush}"/>
    </StackPanel>
</Page>

Para que este ejemplo sea más eficaz, mueva SecondPageTextBrush a SecondPage.xaml y mueva ThirdPageTextBrush a ThirdPage.xaml. InitialPageTextBrush puede permanecer en App.xaml, ya que los recursos de la aplicación deben analizarse en el inicio de la aplicación en cualquier caso.

Unificar varios pinceles que parecen iguales en un solo recurso

La plataforma XAML intenta almacenar en caché objetos usados habitualmente para que se puedan reutilizar con la mayor frecuencia posible. Pero XAML no puede saber fácilmente si un pincel declarado en un fragmento de marcado es el mismo que un pincel declarado en otro. En el ejemplo siguiente se usa solidColorBrush para demostrar, pero el caso es más probable y más importante con GradientBrush. Compruebe también los pinceles que usan colores predefinidos; por ejemplo, "Orange" y "#FFFFA500" son del mismo color.

ineficaz.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page ... >
    <StackPanel>
        <TextBlock>
            <TextBlock.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </TextBlock.Foreground>
        </TextBlock>
        <Button Content="Submit">
            <Button.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </Button.Foreground>
        </Button>
    </StackPanel>
</Page>

Para corregir la duplicación, defina el pincel como un recurso. Si los controles de otras páginas usan el mismo pincel, muévalo a App.xaml.

Eficiente.

<Page ... >
    <Page.Resources>
        <SolidColorBrush x:Key="BrandBrush" Color="#FFFFA500"/>
    </Page.Resources>

    <StackPanel>
        <TextBlock Foreground="{StaticResource BrandBrush}" />
        <Button Content="Submit" Foreground="{StaticResource BrandBrush}" />
    </StackPanel>
</Page>

Minimizar el exceso de dibujo

El sobredibujo se produce cuando se dibujan más de un objeto en los mismos píxeles de la pantalla. Tenga en cuenta que a veces hay un equilibrio entre esta guía y el deseo de minimizar el recuento de elementos.

Use DebugSettings.IsOverdrawHeatMapEnabled como diagnóstico visual. Es posible que encuentre objetos dibujados que no sabía que estaban en la escena.

Elementos transparentes o ocultos

Si un elemento no está visible porque es transparente o oculto detrás de otros elementos, y no contribuye al diseño, elimínelo. Si el elemento no está visible en el estado visual inicial, pero está visible en otros estados visuales, use x:Load para controlar su estado o establezca Visibility en Collapsed en el propio elemento y cambie el valor a Visible en los estados adecuados. Habrá excepciones a esta heurística: en general, el valor que tiene una propiedad en la mayoría de los estados visuales se establece mejor localmente en el elemento.

Elementos compuestos

Use un elemento compuesto en lugar de superponer varios elementos para crear un efecto. En este ejemplo, el resultado es una forma de dos tonos en la que la mitad superior es negra (proveniente del fondo de la cuadrícula Grid) y la mitad inferior es gris (del blanco semitransparente del rectángulo Rectangle mezclado en alfa sobre el fondo negro de la cuadrícula Grid). Aquí se rellenan 150% de los píxeles necesarios para lograr el resultado.

ineficaz.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Black">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Grid.Row="1" Fill="White" Opacity=".5"/>
</Grid>

Eficiente.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Fill="Black"/>
    <Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
</Grid>

Paneles de diseño

Un panel de diseño puede tener dos propósitos: colorear un área y disponer elementos secundarios. Si un elemento más atrás en el orden z ya está coloreando un área, un panel de diseño delante no necesita pintar esa área: en su lugar, simplemente puede enfocarse en organizar sus elementos secundarios. Este es un ejemplo.

ineficaz.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Background="Blue"/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Eficiente.

<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Si el grid de tiene que ser probable, establezca un valor de fondo transparente en él.

Fronteras

Utilice un elemento Border para dibujar un borde alrededor de un objeto. En este ejemplo, se utiliza un Grid como un borde improvisado alrededor de un cuadro de texto . Pero todos los píxeles de la celda central se sobrescriben.

ineficaz.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Blue" Width="300" Height="45">
    <Grid.RowDefinitions>
        <RowDefinition Height="5"/>
        <RowDefinition/>
        <RowDefinition Height="5"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="5"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Row="1" Grid.Column="1"></TextBox>
</Grid>

Eficiente.

<Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
    <TextBox/>
</Border>

Márgenes

Tenga en cuenta los márgenes. Dos elementos vecinos se superponerán (posiblemente accidentalmente) si los márgenes negativos se extienden a los límites de representación de otro y provocarán un exceso de dibujo.

Almacenamiento en caché de contenido estático

Otra fuente de sobrecarga del dibujo es una forma compuesta por muchos elementos superpuestos. Si establece CacheMode en BitmapCache en el UIElement que contiene la forma compuesta, la plataforma representa el elemento como un mapa de bits una vez y luego utiliza ese mapa de bits en cada fotograma en lugar de dibujar en exceso.

ineficaz.

<Canvas Background="White">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

diagrama de Venn con tres círculos sólidos

La imagen anterior es el resultado, pero aquí hay un mapa de las regiones sobregiradas. El rojo más oscuro indica una mayor cantidad de sobregiro.

diagrama de Venn que muestra áreas superpuestas

Eficiente.

<Canvas Background="White" CacheMode="BitmapCache">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Anote el uso de CacheMode. No use esta técnica si alguna de las subformas se anima, ya que es probable que la memoria caché del mapa de bits tenga que volver a generarse en cada fotograma, lo que anula su propósito.

Uso de XBF2

XBF2 es una representación binaria del marcado XAML que evita todos los costos de análisis de texto en tiempo de ejecución. También optimiza el binario para la creación de carga y árbol, y permite "ruta de acceso rápida" para los tipos XAML para mejorar los costos de creación de montón y objetos, por ejemplo, VSM, ResourceDictionary, Styles, etc. Está completamente mapeado a la memoria, por lo que no hay huella de memoria en el montón al cargar y leer una página XAML. Además, reduce la superficie de disco de las páginas XAML almacenadas en un appx. XBF2 es una representación más compacta y puede reducir la superficie de disco de los archivos XAML/XBF1 comparados hasta 50%. Por ejemplo, la aplicación Fotos integrada vio alrededor de una reducción de 60% después de la conversión a XBF2 que cae de aproximadamente ~1 mb de recursos XBF1 a aproximadamente 400 kb de recursos XBF2. También hemos visto que las aplicaciones se benefician entre 15 y 20% en CPU y entre 10 y 15% en el montón Win32.

Los controles y diccionarios integrados xaml que proporciona el marco ya están totalmente habilitados para XBF2. Para su propia aplicación, asegúrese de que el archivo de proyecto declare TargetPlatformVersion 8.2 o posterior.

Para comprobar si tiene XBF2, abra la aplicación en un editor binario; los bytes 12 y 13 son 00 02 si tiene XBF2.