Lidando com os fatores prejudiciais ao desempenho: problemas de desempenho comuns de aplicativos estilo Metro

Blog do desenvolvedor de aplicativos do Windows 8

Ideias sobre a criação de aplicativos com o estilo Metro para o Windows 8, da equipe de engenharia do Windows 8

Lidando com os fatores prejudiciais ao desempenho: problemas de desempenho comuns de aplicativos estilo Metro

  • Comments 0

Agora que você já teve tempo para ler minha postagem anterior, "Como melhor o desempenho em seu aplicativo estilo Metro", sobre a metodologia e as ferramentas disponíveis para ajudá-lo a criar aplicativos rápidos e fluidos, quero falar a respeito dos fatores prejudiciais ao desempenho mais comuns que já vi em aplicativos. Nesta postagem, eu abordo as principais diretrizes que, de acordo com minhas observações, resultam melhorias mensuráveis e notáveis para aplicativos estilo Metro, tanto aqueles desenvolvidos em JavaScript quanto em XAML. Além disso, vou falar sobre cinco práticas específicas que farão uma grande diferença, independentemente da linguagem usada. A boa notícia é que elas não dependem de truques espertos ou operações complexas. Tenho certeza de que essas diretrizes melhorarão significativamente o desempenho do seu aplicativo. Comente para dizer se elas foram úteis e compartilhe suas próprias dicas.

Diretrizes gerais

Confie no conteúdo do pacote, não no conteúdo em rede.

  • A recuperação de imagens e arquivos locais é sempre mais rápida do que a de que estão em rede.
  • Se seu aplicativo deve carregar uma imagem "ao vivo", é melhor usar uma imagem local como um espaço reservado enquanto ela é recuperada.

Dimensione imagens locais para o tamanho correto.

  • Se uma imagem sempre será exibida com a mesma resolução, empacote-a nessa resolução pré-dimensionada. Isso evita que a imagem seja dimensionada durante o tempo de execução sempre que for exibida, o que prejudica o desempenho diversas vezes durante o ciclo de vida do aplicativo.
  • Se a imagem pode ser exibida em várias resoluções, empacote diversas versões da imagem a não ser que haja um bom motivo para não fazer isso.

Mantenha a rapidez dos tempos de inicialização do seu aplicativo moderno.

  • Execute operações de rede somente após a exibição da tela inicial.
  • Adie o carregamento de bancos de dados e de outros objetos grandes da memória enquanto o aplicativo está sendo ativado.
  • Se você precisa executar tarefas grandes, forneça uma tela inicial personalizada ou uma página de destino reduzida para que seu aplicativo possa concluir essas tarefas em segundo plano.

O Windows 8 pode ser executado em uma ampla gama de dispositivos, portanto, use conteúdo apropriado à resolução do usuário.

  • Carregar conteúdo pequeno demais para a resolução de um usuário reduz a fidelidade.
  • Carregar conteúdo grande demais para a resolução de um usuário pode sobrecarregar os recursos do sistema desnecessariamente.

Enfatize a capacidade de resposta de seus aplicativos.

  • Não bloqueie o thread da interface do usuário com APIs sincrônicas. Use APIs assíncronas ou chame APIs sincrônicas em um contexto sem bloqueio (como em outro thread).
  • Mova cálculos com limite de tempo para um thread que pertença ao thread da interface do usuário. Isso é importante pois os usuários provavelmente perceberão atrasos de mais de 100 ms.
  • Divida trabalhos mais demorados em blocos menores, permitindo que o thread da interface do usuário detecte entradas do usuário entre eles.
  • Use trabalhadores web/threads para descarregar trabalhos caros.
  • Não extraia para a tela mais rapidamente do que ela pode ser atualizada. Eventos de entrada são acionados com uma velocidade muito maior do que a taxa de atualização da tela. Portanto, usar esses eventos para atualizar a tela causa uma grande quantidade de trabalho desnecessário. Em vez disso, sincronize as extrações com a taxa de atualização da tela.

Ajustando o desempenho de aplicativos estilo Metro com JavaScript

Ao codificar aplicativos estilo Metro em JavaScript, todas as dicas e truques que você já conhece para codificar JavaScript em sites são aplicáveis. Entretanto, para aplicativos estilo Metro desenvolvidos em JavaScript, apresentamos aqui as 3 principais técnicas que proporcionam os ganhos de desempenho mais significativos com base em nossas pesquisas e observações. Para obter mais informações, consulte Práticas de desempenho recomendadas para aplicativos estilo Metro com JavaScript, no Centro de Desenvolvimento.

Use miniaturas para renderizar rapidamente

O sistema de arquivos e os arquivos de mídia são uma parte importante da maioria dos aplicativos, além de uma das fontes mais comuns de problemas de desempenho. O acesso aos arquivos de mídia pode ser lento, pois são necessários ciclos de memória e de CPU para armazenar e decodificar ou exibir a mídia.

Em vez de reduzir uma versão grande de uma imagem para exibi-la como miniatura, use as APIs de miniatura do Tempo de Execução do Windows. É ineficiente reduzir uma imagem grande, pois o aplicativo deve ler e decodificar a imagem inteira e passar mais tempo dimensionando-a. O Tempo de Execução do Windows oferece um conjunto de APIs, que conta com um cache eficiente para obter rapidamente uma versão menor de uma imagem para usar como miniatura.

Este exemplo solicita uma imagem do usuário e exibe essa imagem.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(file, false);
});

Este exemplo funciona bem quando você deseja renderizar uma imagem em seu tamanho total ou próximo dele, mas é ineficiente para exibir uma exibição em miniatura da imagem. As APIs de miniatura retornam uma versão em miniatura da imagem que o aplicativo pode decodificar e exibir com muito mais rapidez do que a imagem em seu tamanho total. O exemplo seguinte usa o método getThumbnailAsync para recuperar a imagem e criar uma miniatura com base nela.

// Pick an image file
picker.pickSingleFileAsync()
.then(function (file) {
var properties = Windows.Storage.FileProperties.ThumbnailMode;
return file.getThumbnailAsync(properties.singleItem, 1024);
})
.then(function (thumb) {
var imgTag = document.getElementById("imageTag");
imgTag.src = URL.createObjectURL(thumb, false);
});

Nossas medições mostram que esse padrão pode melhorar os tempos de carregamento da imagem em até 1000% (ou seja, a imagem é carregada a uma velocidade até 10 vezes maior ao usar as APIs de miniatura em vez de acessá-la diretamente do disco e dimensioná-la).

Minimize as interações de DOM

Na plataforma para os aplicativos estilo Metro com JavaScript, o DOM e o mecanismo JavaScript são componentes separados. Qualquer operação de JavaScript que envolva comunicação entre esses componentes é relativamente cara em relação a operações que podem ser executadas dentro do tempo de execução do JavaScript. Portanto, é importante minimizar a interação entre esses componentes. Por exemplo, obter ou configurar propriedades de elementos DOM pode ser relativamente caro. Este exemplo acessa a propriedade body e diversas outras propriedades repetidas vezes.

// Don’t use: inefficient code. 
function calculateSum() {
// Retrieve Values
var lSide = document.body.all.lSide.value;
var rSide = document.body.all.rSide.value;

// Generate Result
document.body.all.result.value = lSide + rSide;
}

function updateResultsStyle() {
if (document.body.all.result.value > 10) {
document.body.resultsDiv.class = "highlighted";
} else {
document.body.resultsDiv.class = "normal";
}
}

O exemplo seguinte melhora o código armazenando em cache o valor das propriedades DOM em vez de acessá-las várias vezes.

function calculateSum() {
// Retrieve Values
var all = document.body.all;
var lSide = all.lSide.value;
var rSide = all.rSide.value;

// Generate Result
all.result.value = lSide + rSide;
}

function updateResultsStyle() {
var body = document.body;
var all = body.all;
if (all.result.value > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Use objetos DOM somente para armazenar informações que afetam diretamente como o DOM apresenta ou extrai elementos. Se as propriedades lSide e rSide do exemplo anterior armazenam somente informações sobre o estado interno do aplicativo, não as anexe a um objeto DOM. O exemplo a seguir usa objetos JavaScript puros para armazenar o estado interno do aplicativo. O exemplo atualiza os elementos DOM somente quando é necessário atualizar a exibição.

var state = {
lValue: 0,
rValue: 0,
result: 0
};

function calculateSum() {
state.result = lValue + rValue;
}

function updateResultsStyle() {
var body = document.body;
if (result > 10) {
body.resultsDiv.class = "highlighted";
} else {
body.resultsDiv.class = "normal";
}
}

Nossas medições mostram que simplesmente acessar os dados no DOM pode resultar em um aumento de até 700% no tempo de acesso em comparação ao acesso de variáveis não vinculadas ao DOM.

Gerencie o layout de forma eficiente

Para renderizar um aplicativo na tela, o sistema deve executar um processamento complexo que se aplica às regras de HTML, CSS e outras especificações em relação ao tamanho e à posição dos elementos no DOM. Esse processo é chamado de cálculo de layout e ele pode ser muito caro.

APIs que disparam um cálculo de layout incluem:

  • window.getComputedStyle
  • offsetHeight
  • offsetWidth
  • scrollLeft
  • scrollTop

Um cálculo de layout ocorre como parte da chamada dessas APIs caso qualquer fator que afete o layout tenha sido alterado desde a última coleta de informações de layout. Uma forma de reduzir o número de cálculos de layout é reunir em um lote todas as chamadas de APIs que disparam em um cálculo de layout. Para saber como fazer isso, vamos observar o próximo snippet de código. Nos dois exemplos apresentados, nós ajustamos as propriedades offsetHeight e offsetWidth de um elemento em 5. Primeiro, veja uma forma comum mas ineficiente pode ajustar offsetHeight e offsetWidth:

// Don't use: inefficient code.
function updatePosition(){
// Calculate the layout of this element and retrieve its offsetHeight
var oH = e.offsetHeight;

// Set this element's offsetHeight to a new value
e.offsetHeight = oH + 5;

// Calculate the layout of this element again because it was changed by the
// previous line, and then retrieve its offsetWidth
var oW = e.offsetWidth;

// Set this element's offsetWidth to a new value
e.offsetWidth = oW + 5;
}

// At some later point the Web platform calculates layout again to take this change into account and render
the element to the screen

O exemplo anterior dispara 3 cálculos de layout. Agora veja uma forma melhor de obter o mesmo resultado:

function updatePosition() {
// Calculate the layout of this element and retrieve its offsetHeight
var height = e.offsetHeight + 5;

// Because the previous line already did the layout calculation and no fields were changed, this line retrieves
the offsetWidth from the previous line

var width = e.offsetWidth + 5;

//set this element's offsetWidth to a new value
e.offsetWidth = height;

//set this element's offsetHeight to a new value
e.offsetHeight = width;
}

// At some later point the system calculates layout again to take this change into account and render element to the screen

Mesmo que o segundo exemplo seja apenas um pouco diferente do primeiro, ele dispara somente 2 cálculos de layout em vez de 3, uma melhoria de 33%. O impacto no desempenho varia, dependendo do tamanho e da complexidade do DOM e dos estilos associados. Quando mais complexa for a interface do usuário do seu aplicativo, mais importante será seguir essa diretriz.

Ajustando o desempenho de aplicativos estilo Metro desenvolvidos em XAML

Um XAML bem estruturado apresenta benefícios em diversos cenários importantes, incluindo o tempo de ativação, a navegação de páginas e o uso da memória. Veja algumas dicas para ajudar a ajustar o XAML do seu aplicativo.

Evite a duplicação

Analisar XAML e criar objetos correspondentes na memória pode ser um processo demorado para uma interface do usuário complexa com árvores de elementos detalhadas. Por isso recomendamos que você carregue apenas o XAML necessário para passar do processo de inicialização. A forma mais fácil de fazer isso é carregar somente as páginas necessárias para exibir o primeiro visual. Examine com atenção o XAML da sua primeira página para garantir que ela contenha todos os itens necessários. Se você fizer referência a um controle ou estilo definido em outro lugar, a estrutura também analisa o arquivo em questão.

<!--This is the first page an app displays. A resource used by this page, TextColor, is defined in 
AppStyles.xaml which means that file must be parsed when this page is loaded. Because AppStyles.xaml
contains many app-wide resources, all of these resources need to be parsed even though they aren’t
necessary to start the app. -->

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

Contents of AppStyles.xaml
<ResourceDictionary>
<SolidColorBrush x:Key="TextColor" Color="#FF3F42CC"/>

<!--many other resources used across the app and not necessarily for startup.-->
</ResourceDictionary>

Corte os dicionários de recursos. Armazene recursos que se aplicam a todo o aplicativo no objeto Application para evitar a duplicação, mas mova recursos específicos a páginas individuais para o dicionário de recursos da página em questão. Isso reduz a quantidade de XAML analisado quando o aplicativo é iniciado e incorre somente o custo da análise do XAML quando um usuário navega para a página específica.

<!--Bad: XAML which is specific to a page should not be included in the App’s resource
dictionary. The app incurs the cost of parsing resources that are not immediately
necessary at startup, instead of parsing on demand. These page specific resources should be
moved to the resource dictionary for that page.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="ThirdPageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<StackPanel>
<Button Content="Submit" Foreground="{StaticResource SecondPageColor}" />
</StackPanel>
</Page>
 
<!--Good: All page specific XAML has been moved to the resource dictionary for the
page on which it’s used. The home page specific XAML was not moved because it must
be parsed at start up anyway. Moving it to the resource dictionary of the home page
wouldn’t be bad either.-->
<Application>
<Application.Resources>
<SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/>
<SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/>
</Application.Resources>
</Application>

XAML for the home page of the app
<Page ...>
<StackPanel>
<TextBox Foreground="{StaticResource HomePageTextColor}" />
</StackPanel>
</Page>

XAML for the second page of the app
<Page ...>
<Page.Resources>
<SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/>
</Page.Resources>

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

Otimize a contagem de elementos

A estrutura de XAML foi desenvolvida para exibir milhares de objetos, mas reduzir o número de elementos em uma página tornará o layout do seu aplicativo e as cenas de renderização ainda mais rápidas. Você pode usar alguns truques para reduzir a contagem de elementos de uma cena, mantendo o mesmo nível de complexidade visual.

  • Evite elementos desnecessários. Por exemplo, defina a propriedade Background em painéis para fornecer a cor da tela de fundo em vez de inserir retângulos atrás dos elementos do painel.
<!--Bad XAML uses an unnecessary Rectangle to give the Grid a black background-->                                                                
<Grid>
<Rectangle Fill="Black"/>
</Grid>
 
<!--Good XAML uses the Grid’s background property-->
<Grid Background="Black" />
  • Recolha elementos que não são visíveis por serem bloqueados ou transparentes.

  • Se o mesmo elemento baseado em vetor for reutilizado várias vezes, considere transformá-lo em uma imagem. A memória de uma imagem é alocada somente uma vez por URI de imagem. Em contraste, são criadas instâncias para cada instância de elementos baseados em vetor.

Use animações independentes

Animações podem ser calculadas do início ao fim quando elas são criadas. Às vezes, alterar a propriedade sendo animada não afeta os demais objetos de uma cena. Elas são chamadas de animações independentes e são executadas no thread de composição em vez de no thread da interface do usuário. Isso garante que elas permaneçam uniformes, pois o thread de composição é atualizado em um ritmo consistente. Todos os itens a seguir são independentes:

  • Animações de objetos que usam quadros chave
  • Animações de duração zero
  • Canvas.Left/Top
  • UIElement.Opacity
  • SolidColorBrush.Color quando se aplica a várias propriedades
  • As subpropriedades de
    • UIElement.RenderTransform
    • UIElement.Projection
    • RectangleGeometry de UIElement.Clip

Animações dependentes afetam o layout e não podem ser calculadas sem entradas adicionais do thread da interface do usuário. Essas animações incluem modificações a propriedades como largura e altura. Por padrão, animações dependentes não são executadas e exigem a aceitação do desenvolvedor do aplicativo. Quando habilitadas, elas são executadas de forma uniforme se o thread da interface do usuário permanecer desbloqueado. Entretanto, elas começam a apresentar interferência se a estrutura ou o aplicativo estiver executando diversos outros trabalhos.

A estrutura de XAML se esforçou para tornar todas as animações independentes por padrão. No entanto, você pode executar algumas ações para desabilitar essa otimização. Tenha muito cuidado ao executar as seguintes ações:

  • Definir a propriedade EnableDependentAnimation permite a execução de uma animação dependente no thread da interface do usuário. Converta essas animações em uma versão independente. Por exemplo, anime ScaleTransform.XScale e ScaleTransform.YScale em vez das propriedades Width e Height de um objeto.
  • Criar atualizações por quadro que são efetivamente animações dependente. Um exemplo disso é aplicar transformações ao manipulador de CompositonTarget.Rendering.
  • Executar qualquer animação considerada independente em um elemento com CacheMode=BitmapCache. Ela é considerada dependente porque o cache deve ser rerasterizado a cada quadro.

Criar aplicativos rápidos e fluidos que sejam avançados e complexos é uma arte. Eu compartilhei alguns itens específicos para que você considere, mas uma ferramenta ou técnica individual raramente é a chave para um bom desempenho. Frequentemente, você deve considerar um conjunto de práticas recomendadas em relação ao desempenho antecipadamente no processo de desenvolvimento. Espero que este blog indique o caminho certo para que você atenda às altas expectativas dos seus clientes.

Boa (e eficiente) codificação!

-Dave Tepper, gerente de programas, Windows

  • Loading...
Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post