Beheben von Leistungshemmern: Verbreitete Leistungsprobleme bei Apps im Metro-Stil

Entwicklerblog für Windows 8-Apps

Ein Einblick in die Entwicklung von Apps im Metro-Stil – präsentiert vom Windows 8-Entwicklerteam

Beheben von Leistungshemmern: Verbreitete Leistungsprobleme bei Apps im Metro-Stil

  • Comments 3

In meinem letzten Beitrag „So verbessern Sie die Leistung von Apps im Metro-Stil“ habe ich Methoden und Tools erläutert, die Ihnen beim Erstellen von schnellen und flüssigen Apps zur Verfügung stehen. Nun möchte ich mich eingehend mit verbreiteten Leistungshemmern beschäftigen, die mir in Apps aufgefallen sind. Ich werde die wichtigsten Orientierungspunkte behandeln, die meiner Erfahrung nach zu merklichen und messbaren Verbesserungen von Apps im Metro-Stil führen. Dies gilt sowohl für JavaScript- als auch für XAML-Apps. Außerdem stelle ich fünf spezifische Vorgehensweisen vor, die sich unabhängig von der verwendeten Sprache erwiesenermaßen positiv auswirken. Dafür sind weder raffinierte Tricks noch komplexe Arbeitsschritte erforderlich. Ich bin mir sicher, dass Sie mithilfe dieser Richtlinien die Leistung Ihrer App deutlich steigern können. Ich freue mich über Kommentare zu diesen Richtlinien und selbstverständlich auch über Ihre eigenen Tipps.

Allgemeine Richtlinien

Verwenden Sie Inhaltspakete anstelle von Netzwerkinhalten.

  • Lokal gespeicherte Bilder und Dateien können stets schneller geladen werden als Netzwerkinhalte.
  • Wenn Ihre App ein in Echtzeit aktualisiertes Bild laden muss, ist es ratsam, während des Ladevorgangs ein lokal gespeichertes Bild als Platzhalter zu verwenden.

Skalieren Sie lokal gespeicherte Bilder auf die richtige Größe.

  • Wenn ein Bild immer in der gleichen Auflösung dargestellt wird, sollte es mit bereits dieser vorskalierten Auflösung im Paket enthalten sein. So muss das Bild nicht jedes Mal während der Laufzeit skaliert und damit die Leistung der App wiederholt beeinträchtigt werden.
  • Sofern keine wichtigen Gründe dagegen sprechen, sollte das Paket mehrere Versionen des Bilds enthalten, wenn dieses in unterschiedlichen Auflösungen angezeigt werden soll.

Gewährleisten Sie eine kurze Startzeit der App.

  • Führen Sie Netzwerkvorgänge erst durch, nachdem der Begrüßungsbildschirm angezeigt wurde.
  • Unterbinden Sie das Laden von Datenbanken und anderen großen Speicherobjekten, während die App aktiviert wird.
  • Wenn umfangreiche Tasks ausgeführt werden müssen, stellen Sie einen angepassten Begrüßungsbildschirm oder eine minimalisierte Angebotsseite zur Verfügung, sodass Ihre App diese Tasks im Hintergrund ausführen kann.

Windows 8 kann auf unterschiedlichsten Geräten ausgeführt werden. Verwenden Sie Medieninhalte, die der Auflösung des jeweiligen Bildschirms angemessen sind.

  • Das Laden von Inhalten, die zu klein für die entsprechende Auflösung sind, beeinträchtigt die Darstellung.
  • Das Laden von Inhalten, die zu groß für die entsprechende Auflösung sind, beansprucht die Systemressourcen.

Optimieren Sie das Reaktionsverhalten Ihrer Apps.

  • Blockieren Sie den UI-Thread nicht mit synchronen APIs. Verwenden Sie asynchrone APIs, oder rufen Sie synchrone APIs in einem nicht blockierenden Kontext (z. B. aus einem anderen Thread) auf.
  • Führen Sie umfangreiche Berechnungen nicht im UI-Thread aus. Dies ist wichtig, da Benutzer Verzögerungen von über 100 ms wahrnehmen.
  • Teilen Sie zeitaufwendige Aufgaben in mehrere kleinere Einheiten auf, sodass der UI-Thread in der Zwischenzeit auf Benutzereingaben reagieren kann.
  • Verwenden Sie für ressourcenintensive Aufgaben Web-Worker/-Threads.
  • Die Aktualisierungsrate des Bildschirms ist begrenzt. Eingabeereignisse werden wesentlich schneller ausgeführt, als der Bildschirm aktualisiert werden kann. Wenn Sie diese Ereignisse dazu verwenden, den Bildschirm zu aktualisieren, verursachen Sie unnötigen Arbeitsaufwand. Passen Sie stattdessen die Darstellungsvorgänge an die Aktualisierungsrate des Bildschirms an.

Optimieren der Leistung von mit JavaScript erstellten Apps im Metro-Stil

Beim Erstellen von JavaScript-Apps im Metro-Stil gelten auch weiterhin alle Tipps und Tricks, die Sie von der JavaScript-Programmierung für Websites kennen. Für Apps im Metro-Stil, die mit JavaScript erstellt wurden, gibt es jedoch drei wichtige, zusätzliche Methoden. Diese ermöglichen gemäß unserer Erfahrung besonders deutliche Leistungssteigerungen. Weitere Informationen finden Sie im Entwicklungscenter unter Best Practices für die Leistung von Apps im Metro-Stil mit JavaScript.

Schnelles Rendering mithilfe von Miniaturansichten

Das Dateisystem und die Mediendateien sind ein wichtiger Bestandteil der meisten Apps und eine häufige Ursache für Leistungsprobleme. Da Speicher- und CPU-Ressourcen zum Speichern, Dekodieren und Anzeigen der Medien erforderlich sind, kann dies den Zugriff auf Mediendateien verlangsamen.

Verwenden Sie die Miniaturansicht-APIs von Windows-Runtime, statt eine große Bildversion zu einer Miniaturansicht herunterzuskalieren. Ein großes Bild herunterzuskalieren ist eine ineffiziente Methode. Denn erst muss die App das Bild in voller Größe lesen und dekodieren und anschließend zusätzliche Zeit und Ressourcen für die Skalierung aufwenden. Windows-Runtime stellt eine Reihe von APIs zur Verfügung, die mithilfe eines effizienten Caches rasch eine kleinere Bildversion bereitstellen, die von Apps als Miniaturansicht verwendet werden kann.

In diesem Beispiel wird der Benutzer aufgefordert, ein Bild auszuwählen, das anschließend angezeigt wird.

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

Dieses Beispiel ist gut geeignet, wenn das Bild in Originalgröße oder beinahe in Originalgröße gerendert werden soll. Es ist jedoch ineffizient, um eine Miniaturansicht des Bilds anzuzeigen. Die Miniaturansicht-APIs geben eine Miniaturansicht des Bilds zurück, die von der App wesentlich schneller dekodiert und abgebildet werden kann als das Bild in voller Größe. Das nächste Beispiel ruft mithilfe der getThumbnailAsync-Methode das Bild ab und erstellt auf dieser Grundlage eine Miniaturansicht.

// 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);
});

Unsere Messungen zeigen, dass dieses Schema die Bildladezeit um bis zu 1000 % verbessern kann (d. h., bei der Verwendung der Miniaturansichts-API lädt das Bild bis zu zehnmal schneller, als wenn das Bild direkt von der Festplatte geladen und dann skaliert wird.)

Minimieren von DOM-Interaktionen

In der Plattform für Apps im Metro-Stil auf Grundlage von JavaScript sind das DOM- und das JavaScript-Modul separate Komponenten. Jeder JavaScript-Vorgang, für den Kommunikation zwischen diesen beiden Komponenten erforderlich ist, benötigt im Vergleich zu Vorgängen, die ausschließlich innerhalb der JavaScript-Laufzeit ausgeführt werden können, deutlich mehr Ressourcen. Daher ist es wichtig, Interaktionen zwischen diesen Komponenten auf ein Minimum zu begrenzen. Beispielsweise kann das Abrufen oder Festlegen von Eigenschaften von DOM-Elementen recht aufwendig sein. In diesem Beispiel wird wiederholt auf die body-Eigenschaft und verschiedene weitere Eigenschaften zugegriffen.

// 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";
}
}

Das folgende Beispiel verbessert den Code, indem der Wert der DOM-Eigenschaften zwischengespeichert wird, anstatt auf diese wiederholt zuzugreifen.

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";
}
}

Verwenden Sie DOM-Objekte ausschließlich, um Informationen zu speichern, die einen unmittelbaren Einfluss darauf haben, wie durch das DOM Elemente angeordnet oder gezeichnet werden. Wenn die lSide- und rSide-Eigenschaften des vorhergehenden Beispiels ausschließlich Informationen über den internen Status der App speichern, sollten Sie diese nicht an ein DOM-Objekt anfügen. Im folgenden Beispiel wird der interne Status der App mithilfe reiner JavaScript-Objekte gespeichert. Hier werden DOM-Elemente nur dann aktualisiert, wenn der Bildschirm aktualisiert werden muss.

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";
}
}

Unsere Messungen ergeben, dass sich alleine durch den Zugriff auf Daten im DOM eine Verlängerung der Zugriffszeit von bis zu 700 % gegenüber dem Zugriff auf Daten ergeben kann, die nicht an das DOM gebunden sind.

Effizientes Verwalten des Layouts

Um eine App auf dem Bildschirm zu rendern, muss durch das System eine komplexe Verarbeitung durchgeführt werden, in der die in HTML, im CSS und in anderen Spezifikationen festgelegten Regeln in Bezug auf Größe und Position der Elemente im DOM angewendet werden. Dieser Prozess wird als ein Layoutdurchlauf bezeichnet und kann sehr aufwendig werden.

APIs, die einen Layoutdurchlauf auslösen, sind z. B.:

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

Ein Layoutdurchlauf ist Bestandteil des Aufrufs dieser APIs, wenn nach dem Abrufen der Layoutinformationen eine Änderung stattfindet, durch die das Layout beeinflusst wird. Eine Möglichkeit, die Anzahl der Layoutdurchläufe zu verringern, besteht in der Stapelverarbeitung der API-Aufrufe, die einen Layoutdurchlauf auslösen. Um das entsprechende Vorgehen zu verstehen, betrachten wir den folgenden Codeausschnitt. In den beiden Beispielen werden die Werte eines Elements für offsetHeight und offsetWidth um 5 verändert. Werfen Sie zunächst einen Blick auf einen üblichen jedoch ineffektiven Weg zur Veränderung von offsetHeight und 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

Im vorhergehenden Beispiel wurden drei Layoutdurchläufe ausgelöst. Betrachteten wir nun eine bessere Methode, um das gleiche Resultat zu erzielen:

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

Obwohl sich das zweite Beispiel nur geringfügig vom ersten unterscheidet, werden dort lediglich zwei anstatt drei Layoutdurchläufe ausgelöst – eine Verbesserung um 33 %. Die Auswirkung auf die Leistung ist von der Größe und Komplexität des DOM und der damit verbundenen Stile abhängig. Diese Richtlinie ist um so bedeutender, je umfangreicher die Benutzeroberfläche Ihrer App gestaltet ist.

Optimieren der Leistung von mit XAML erstellten Apps im Metro-Stil

Gut strukturiertes XAML ist in vielen wichtigen Szenarien von Vorteil, einschließlich Aktivierungszeit, Seitennavigation und Speicherauslastung. Hier finden Sie verschiedene Hinweise für die Optimierung von XAML in Ihrer App.

Vermeiden von Duplizierung

Das Analysieren von XAML und die Erstellung entsprechender Objekte im Speicher kann bei komplexen Benutzeroberflächen mit tiefer Elementstruktur zeitaufwendig sein. Daher empfehlen wir, lediglich das für den Startvorgang erforderliche XAML zu laden. Am einfachsten ist es, nur die Seiten zu laden, die für die erste Anzeige erforderlich sind. Untersuchen Sie das XAML der ersten Seite sorgfältig, um sicherzustellen, dass alle erforderlichen Komponenten vorhanden sind. Wenn Sie einen Verweis auf Steuerelemente oder Stile verwenden, die an einer anderen Stelle definiert sind, wird die entsprechende Datei durch das Framework ebenfalls analysiert.

<!--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>

Reduzieren Sie Ressourcenverzeichnisse. Speichern Sie Ressourcen, die in der gesamten App verwendet werden, zur Vermeidung von Duplizierungen im Application-Objekt. Verschieben Sie jedoch die Ressourcen, die spezifisch für einzelne Seiten sind, in das Ressourcenverzeichnis der entsprechenden Seite. Dies verringert beim Start der App den Umfang an zu analysierendem XAML. Die entsprechenden XAML-Komponenten müssen nur dann analysiert werden, wenn ein Benutzer diese spezifische Seite aufruft.

<!--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>

Optimieren der Anzahl der Elemente

Das XAML-Framework ist darauf ausgelegt, zahlreiche Objekte anzuzeigen. Wenn Sie jedoch die Anzahl der Elemente auf einer Seite verringern, können Sie Layout der App und das Szenenrendering weiter beschleunigen. Mit verschiedenen Tricks können Sie unter Beibehaltung der visuellen Komplexität die Anzahl der Elemente einer Szene verringern.

  • Vermeiden Sie überflüssige Elemente. Legen Sie beispielsweise in Panels die Background-Eigenschaft fest, um eine Hintergrundfarbe zu bestimmen, anstatt farbige Rechtecke hinter den Elementen in dem entsprechenden Panel zu platzieren.
<!--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" />
  • Reduzieren Sie Elemente, die blockiert oder transparent und somit unsichtbar sind.

  • Wenn das gleiche sektorbasierte Element mehrfach verwendet wird, sollten Sie die Umwandlung in ein Bild in Betracht ziehen. Der Speicher für ein Bild wird nur einmal pro Bild-URI zugewiesen. Im Gegensatz dazu wird für vektorbasierte Elemente jedes Mal eine gesonderte Instanz erstellt.

Verwenden unabhängiger Animationen

Animationen können nach der Erstellung von Anfang bis Ende berechnet werden. Manchmal sind durch die Änderungen einer animierten Eigenschaft die übrigen Objekte einer Szene nicht betroffen. Diese werden als unabhängige Animationen bezeichnet und im Kompositions-Thread anstelle des UI-Threads ausgeführt. Dadurch wird deren reibungsloser Ablauf gewährleistet, da der Kompositions-Thread in einem konstanten Intervall aktualisiert wird. Die folgenden Animationen sind alle garantiert unabhängig:

  • Objektanimationen mit Keyframes
  • Animationen mit einer Dauer von null
  • Canvas.Left/Top
  • UIElement.Opacity
  • SolidColorBrush.Color, bei Anwendung auf viele Eigenschaften
  • Untereigenschaften von
    • UIElement.RenderTransform
    • UIElement.Projection
    • RectangleGeometry von UIElement.Clip

Abhängige Animationen beeinflussen das Layout und können ohne eine zusätzliche Eingabe vom UI-Thread nicht berechnet werden. Diese Animationen umfassen Änderungen an Eigenschaften wie z. B. Breite und Höhe. Standardmäßig werden abhängige Animationen nicht ausgeführt und erfordern ein Abonnement vom App-Entwickler. Die Animationen werden bei Aktivierung reibungslos ausgeführt, wenn der UI-Thread nicht blockiert wird. Allerdings treten bei einer anderweitig hohen Auslastung des Frameworks oder der App Unterbrechungen auf.

Das XAML-Framework kann nun fast alle Animationen standardmäßig unabhängig gestalten. Es stehen allerdings einige Aktionen zur Verfügung, um diese Optimierung zu deaktivieren. Gehen Sie bei folgenden Schritten mit äußerster Vorsicht vor:

  • Die Festlegung der EnableDependentAnimation-Eigenschaft ermöglicht die Ausführung einer abhängigen Animation im UI-Thread. Konvertieren Sie diese Animationen in eine unabhängige Version. Animieren Sie zum Beispiel ScaleTransform.XScale und ScaleTransform.YScale anstelle von Width und Height eines Objekts.
  • Führen Sie Aktualisierungen pro Frame aus, was faktisch einer abhängigen Animation entspricht. Ein Beispiel hierfür ist das Anwenden von Transformationen im Handler für CompositonTarget.Rendering.
  • Führen Sie als unabhängig angesehene Animationen in einem Element mit CacheMode=BitmapCache aus. Dies wird als abhängig aufgefasst, da der Cache für jeden Frame erneut gerastert werden muss.

Die Erstellung von schnellen und flüssigen Apps, die reichhaltig und komplex sind, erfordert hohe Fertigkeit. Ich habe Ihnen verschiedene Aspekte als Denkanstoß vorgestellt. Allerdings ist ein einziges Tool oder eine einzige Methode selten der Schlüssel zu guter Leistung. Meist handelt es sich um die Kombination bewährter Verfahren, vor allem, wenn Sie bereits in der Frühphase der Entwicklung Leistungsfragen berücksichtigen. Hoffentlich unterstützt Sie dieser Blog bei der Erfüllung der hohen Kundenerwartungen.

Viel Spaß (und viel Erfolg) beim Erstellen effizienter Anwendungen!

– Dave Tepper, Programmmanager, Windows

  • Loading...
Leave a Comment
  • Please add 6 and 7 and type the answer here:
  • Post