Android bietet integrierte Unterstützung für SQLite, eine effiziente SQL-Datenbank. Befolgen Sie diese Best Practices, um die Leistung Ihrer Anwendung zu optimieren und dafür zu sorgen, dass sie auch beim Anwachsen Ihrer Daten schnell und vorhersehbar schnell bleibt. Mit diesen Best Practices verringern Sie auch die Wahrscheinlichkeit von Leistungsproblemen, die schwer zu reproduzieren und zu beheben sind.
Beachten Sie die folgenden Leistungskriterien, um eine höhere Leistung zu erzielen:
Weniger Zeilen und Spalten lesen: Optimieren Sie Ihre Abfragen so, dass nur die erforderlichen Daten abgerufen werden. Minimieren Sie die Menge der aus der Datenbank gelesenen Daten, da ein zu vieler Datenabrufe die Leistung beeinträchtigen können.
Arbeiten in SQLite-Engine übertragen: Führen Sie Berechnungen, Filterung und Sortiervorgänge innerhalb der SQL-Abfragen aus. Die Verwendung der Abfrage-Engine von SQLite kann die Leistung erheblich verbessern.
Datenbankschema ändern: Entwerfen Sie Ihr Datenbankschema, damit SQLite effiziente Abfragepläne und Datendarstellungen erstellen kann. Indexieren Sie Tabellen ordnungsgemäß und optimieren Sie die Tabellenstrukturen, um die Leistung zu verbessern.
Darüber hinaus können Sie die verfügbaren Tools zur Fehlerbehebung verwenden, um die Leistung Ihrer SQLite-Datenbank zu messen und Bereiche zu ermitteln, die optimiert werden müssen.
Wir empfehlen die Verwendung der Jetpack Room Library.
Datenbank für Leistung konfigurieren
Führen Sie die Schritte in diesem Abschnitt aus, um Ihre Datenbank für optimale Leistung in SQLite zu konfigurieren.
Write-Ahead-Logging aktivieren
SQLite implementiert Mutationen, indem es sie an ein Log anhängt, wodurch gelegentlich die Datenbank komprimiert wird. Dies wird als Write-Ahead-Logging (WAL) bezeichnet.
Aktivieren Sie WAL, es sei denn, Sie verwenden ATTACH
DATABASE
.
Synchronisierungsmodus lockern
Bei Verwendung von WAL wird standardmäßig bei jedem Commit ein fsync
ausgegeben, um dafür zu sorgen, dass die Daten das Laufwerk erreichen. Dies verbessert die Langlebigkeit der Daten, verlangsamt jedoch Ihre Commits.
SQLite bietet eine Option zur Steuerung des synchronen Modus. Wenn Sie WAL aktivieren, legen Sie den synchronen Modus auf NORMAL
fest:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
Bei dieser Einstellung kann ein Commit zurückgegeben werden, bevor die Daten auf einem Laufwerk gespeichert wurden. Wenn ein Gerät heruntergefahren wird, z. B. bei einem Stromausfall oder einer Kernel-Panic, gehen die übergebenen Daten möglicherweise verloren. Aufgrund des Loggings ist Ihre Datenbank jedoch nicht beschädigt.
Wenn nur Ihre App abstürzt, erreichen Ihre Daten trotzdem das Laufwerk. Bei den meisten Anwendungen führt diese Einstellung zu Leistungsverbesserungen, ohne dass erhebliche Kosten anfallen.
Effiziente Tabellenschemas definieren
Definieren Sie ein effizientes Tabellenschema, um die Leistung zu optimieren und den Datenverbrauch zu minimieren. SQLite erstellt effiziente Abfragepläne und Daten, die zu einem schnelleren Datenabruf führen. Dieser Abschnitt enthält Best Practices zum Erstellen von Tabellenschemas.
INTEGER PRIMARY KEY
verwenden
Definieren und füllen Sie für dieses Beispiel eine Tabelle wie folgt:
CREATE TABLE Customers(
id INTEGER,
name TEXT,
city TEXT
);
INSERT INTO Customers Values(456, 'John Lennon', 'Liverpool, England');
INSERT INTO Customers Values(123, 'Michael Jackson', 'Gary, IN');
INSERT INTO Customers Values(789, 'Dolly Parton', 'Sevier County, TN');
Die Tabellenausgabe sieht so aus:
Zeilen-ID | id | Name | Stadt |
---|---|---|---|
1 | 456 | John Lennon | Liverpool, England |
2 | 123 | Michael Jackson | Gary, IN |
3 | 789 | Dolly Parton | Sevier County, Tennessee |
Die Spalte rowid
ist ein Index, bei dem der Anzeigenauftrag beibehalten wird. Abfragen, die nach rowid
filtern, werden als schnelle Suche im B-Baum implementiert. Abfragen, die nach id
filtern, sind jedoch ein langsamer Tabellenscan.
Wenn Sie Suchvorgänge nach id
ausführen möchten, können Sie das Speichern der Spalte rowid
vermeiden, um weniger Daten zu speichern und eine insgesamt schnellere Datenbank zu erhalten:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
Die Tabelle sieht jetzt so aus:
id | Name | Stadt |
---|---|---|
123 | Michael Jackson | Gary, IN |
456 | John Lennon | Liverpool, England |
789 | Dolly Parton | Sevier County, Tennessee |
Da Sie die Spalte rowid
nicht speichern müssen, sind id
-Abfragen schnell. Die Tabelle ist jetzt nach id
und nicht mehr nach Anzeigenauftrag sortiert.
Abfragen mit Indexen beschleunigen
SQLite nutzt Indexe, um Abfragen zu beschleunigen. Beim Filtern (WHERE
), Sortieren (ORDER BY
) oder Aggregieren (GROUP BY
) einer Spalte wird die Abfrage beschleunigt, wenn die Tabelle einen Index für die Spalte enthält.
Im vorherigen Beispiel muss zum Filtern nach city
die gesamte Tabelle gescannt werden:
SELECT id, name
WHERE city = 'London, England';
Bei einer Anwendung mit vielen Städteabfragen können Sie diese Abfragen mit einem Index beschleunigen:
CREATE INDEX city_index ON Customers(city);
Ein Index wird als zusätzliche Tabelle implementiert, nach der Indexspalte sortiert und rowid
zugeordnet:
Stadt | Zeilen-ID |
---|---|
Gary, IN | 2 |
Liverpool, England | 1 |
Sevier County, Tennessee | 3 |
Die Speicherkosten für die Spalte city
sind jetzt doppelt so hoch, weil sie jetzt sowohl in der ursprünglichen Tabelle als auch im Index vorhanden ist. Da Sie den Index verwenden, sind die Kosten für zusätzlichen Speicher den Vorteil schnellerer Abfragen wert.
Verwalten Sie jedoch keinen Index, den Sie nicht verwenden, um zu vermeiden, dass Sie die Speicherkosten bezahlen, ohne dass die Abfrageleistung gesteigert wird.
Mehrspaltige Indexe erstellen
Wenn in Ihren Abfragen mehrere Spalten kombiniert werden, können Sie mehrspaltige Indexe erstellen, um die Abfrage vollständig zu beschleunigen. Sie können auch einen Index für eine externe Spalte verwenden und die interne Suche als linearen Scan durchführen lassen.
Angenommen, die folgende Abfrage würde lauten:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Sie können die Abfrage mit einem mehrspaltigen Index in der gleichen Reihenfolge wie in der Abfrage angeben beschleunigen:
CREATE INDEX city_name_index ON Customers(city, name);
Wenn Sie jedoch nur einen Index für city
haben, wird die äußere Reihenfolge dennoch beschleunigt, während die interne Reihenfolge einen linearen Scan erfordert.
Dies funktioniert auch mit Präfixanfragen. Ein Index-ON Customers (city, name)
beschleunigt zum Beispiel auch das Filtern, Sortieren und Gruppieren nach city
, da die Indextabelle für einen mehrspaltigen Index nach den angegebenen Indexen in der angegebenen Reihenfolge geordnet wird.
WITHOUT ROWID
verwenden
Standardmäßig erstellt SQLite eine rowid
-Spalte für die Tabelle, wobei rowid
ein impliziter INTEGER PRIMARY KEY AUTOINCREMENT
-Wert ist. Wenn Sie bereits eine Spalte mit INTEGER PRIMARY KEY
haben, wird diese Spalte zu einem Alias von rowid
.
Für Tabellen mit einem anderen Primärschlüssel als INTEGER
oder einem aus mehreren Spalten zusammengesetzten Primärschlüssel kann WITHOUT
ROWID
verwendet werden.
Speichern Sie kleine Daten als BLOB
und große Daten als Datei.
Wenn Sie einer Zeile große Daten zuordnen möchten, z. B. eine Miniaturansicht eines Bildes oder ein Foto für einen Kontakt, können Sie die Daten entweder in einer BLOB
-Spalte oder in einer Datei und dann den Dateipfad in der Spalte speichern.
Dateien werden in der Regel auf 4-KB-Schritte aufgerundet. Bei sehr kleinen Dateien, bei denen der Rundungsfehler erheblich ist, sollten sie effizienter als BLOB
in der Datenbank gespeichert werden. SQLite minimiert Dateisystemaufrufe und ist in einigen Fällen schneller als das zugrunde liegende Dateisystem.
Abfrageleistung verbessern
Befolgen Sie diese Best Practices, um die Abfrageleistung in SQLite zu verbessern, indem Sie die Antwortzeiten minimieren und die Verarbeitungseffizienz maximieren.
Nur die benötigten Zeilen lesen
Mit Filtern können Sie die Ergebnisse eingrenzen, indem Sie bestimmte Kriterien angeben, z. B. Zeitraum, Ort oder Name. Mit Limits können Sie die Anzahl der angezeigten Ergebnisse festlegen:
Kotlin
db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """, null)) { while (cursor.moveToNext()) { ... } }
Nur die benötigten Spalten lesen
Vermeiden Sie die Auswahl nicht benötigter Spalten, da dies Ihre Abfragen verlangsamen und Ressourcen verschwenden kann. Wählen Sie stattdessen nur verwendete Spalten aus.
Im folgenden Beispiel wählen Sie id
, name
und phone
aus:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT id, name, phone FROM customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(1) // ... } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name, phone FROM customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(1); ... } }
Sie benötigen jedoch nur die Spalte name
:
Kotlin
db.rawQuery(""" SELECT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(0) ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(0); ... } }
Verwenden Sie DISTINCT
für eindeutige Werte
Mit dem Schlüsselwort DISTINCT
können Sie die Leistung Ihrer Abfragen verbessern, da weniger Daten verarbeitet werden müssen. Wenn Sie beispielsweise nur die eindeutigen Werte aus einer Spalte zurückgeben möchten, verwenden Sie DISTINCT
:
Kotlin
db.rawQuery(""" SELECT DISTINCT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { // Only iterate over distinct names in Kotlin ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT DISTINCT name FROM Customers; """, null)) { while (cursor.moveToNext()) { // Only iterate over distinct names in Java ... } }
Nach Möglichkeit Aggregatfunktionen verwenden
Aggregatfunktionen verwenden, um Ergebnisse ohne Zeilendaten zu aggregieren Mit dem folgenden Code wird beispielsweise geprüft, ob es mindestens eine übereinstimmende Zeile gibt:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """, null)) { if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Um nur die erste Zeile abzurufen, können Sie EXISTS()
verwenden, um 0
zurückzugeben, wenn keine übereinstimmende Zeile vorhanden ist, und 1
, wenn eine oder mehrere Zeilen übereinstimmen:
Kotlin
db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris'; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris' ); """, null)) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Verwenden Sie die SQLite-Aggregatfunktionen im App-Code:
COUNT
: zählt, wie viele Zeilen sich in einer Spalte befinden.SUM
: addiert alle numerischen Werte in einer Spalte.MIN
oderMAX
: Legt den niedrigsten oder höchsten Wert fest. Funktioniert für numerische Spalten,DATE
-Typen und Texttypen.AVG
: ermittelt den numerischen Durchschnittswert.GROUP_CONCAT
: verkettet Strings mit einem optionalen Trennzeichen.
COUNT()
statt Cursor.getCount()
verwenden
Im folgenden Beispiel liest die Funktion Cursor.getCount()
alle Zeilen aus der Datenbank und gibt alle Zeilenwerte zurück:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id FROM Customers; """.trimIndent(), null ).use { cursor -> val count = cursor.getCount() }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id FROM Customers; """, null)) { int count = cursor.getCount(); ... }
Bei Verwendung von COUNT()
gibt die Datenbank jedoch nur die Anzahl zurück:
Kotlin
db.rawQuery(""" SELECT COUNT(*) FROM Customers; """.trimIndent(), null ).use { cursor -> cursor.moveToFirst() val count = cursor.getInt(0) }
Java
try (Cursor cursor = db.rawQuery(""" SELECT COUNT(*) FROM Customers; """, null)) { cursor.moveToFirst(); int count = cursor.getInt(0); ... }
Nest-Abfragen anstelle von Code
SQL ist zusammensetzbar und unterstützt Unterabfragen, Joins und Fremdschlüsseleinschränkungen. Sie können das Ergebnis einer Abfrage in einer anderen Abfrage verwenden, ohne Anwendungscode zu durchlaufen. Dies reduziert die Notwendigkeit, Daten aus SQLite zu kopieren, und lässt die Datenbank-Engine Ihre Abfrage optimieren.
Im folgenden Beispiel können Sie eine Abfrage ausführen, um zu ermitteln, welche Stadt die meisten Kunden hat. Das Ergebnis können Sie dann in einer anderen Abfrage verwenden, um alle Kunden aus dieser Stadt zu finden:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { val topCity = cursor.getString(0) db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """.trimIndent(), arrayOf(topCity)).use { innerCursor -> while (innerCursor.moveToNext()) { ... } } } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """, null)) { if (cursor.moveToFirst()) { String topCity = cursor.getString(0); try (Cursor innerCursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """, new String[] {topCity})) { while (innerCursor.moveToNext()) { ... } } } }
Um das Ergebnis in der Hälfte der Fälle des vorherigen Beispiels zu erhalten, verwenden Sie eine einzelne SQL-Abfrage mit verschachtelten Anweisungen:
Kotlin
db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT (*) DESC LIMIT 1; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1 ); """, null)) { while(cursor.moveToNext()) { ... } }
Eindeutigkeit in SQL prüfen
Wenn eine Zeile nur dann eingefügt werden darf, wenn ein bestimmter Spaltenwert in der Tabelle eindeutig ist, kann es effizienter sein, diese Eindeutigkeit als Spalteneinschränkung zu erzwingen.
Im folgenden Beispiel wird eine Abfrage ausgeführt, um die einzufügende Zeile zu validieren, und eine weitere, um sie tatsächlich einzufügen:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """.trimIndent(), arrayOf(customer.username) ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw AddCustomerException(customer) } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", arrayOf( customer.id.toString(), customer.name, customer.username ) )
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """, new String[] { customer.username })) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw new AddCustomerException(customer); } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, });
Anstatt die eindeutige Einschränkung in Kotlin oder Java zu prüfen, können Sie sie beim Definieren der Tabelle auch in SQL prüfen:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite funktioniert so:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Jetzt können Sie eine Zeile einfügen und SQLite die Einschränkung überprüfen lassen:
Kotlin
try { db.execSql( "INSERT INTO Customers VALUES (?, ?, ?)", arrayOf(customer.id.toString(), customer.name, customer.username) ) } catch(e: SQLiteConstraintException) { throw AddCustomerException(customer, e) }
Java
try { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, }); } catch (SQLiteConstraintException e) { throw new AddCustomerException(customer, e); }
SQLite unterstützt eindeutige Indexe mit mehreren Spalten:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite validiert Einschränkungen schneller und mit weniger Aufwand als Kotlin- oder Java-Code. Es empfiehlt sich, SQLite anstelle von App-Code zu verwenden.
Mehrere Einfügungen in einer einzigen Transaktion im Batch hinzufügen
Eine Transaktion übernimmt mehrere Vorgänge, was nicht nur die Effizienz, sondern auch die Richtigkeit verbessert. Um die Datenkonsistenz zu verbessern und die Leistung zu beschleunigen, können Sie Einfügungen im Batch ausführen:
Kotlin
db.beginTransaction() try { customers.forEach { customer -> db.execSql( "INSERT INTO Customers VALUES (?, ?, ...)", arrayOf(customer.id.toString(), customer.name, ...) ) } } finally { db.endTransaction() }
Java
db.beginTransaction(); try { for (customer : Customers) { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ...)", new String[] { String.valueOf(customer.id), customer.name, ... }); } } finally { db.endTransaction() }
Tools zur Fehlerbehebung verwenden
SQLite bietet die folgenden Tools zur Fehlerbehebung, mit denen Sie die Leistung messen können.
Interaktive Aufforderung von SQLite verwenden
Führen Sie SQLite auf Ihrem Computer aus, um Abfragen auszuführen und zu lernen.
Verschiedene Android-Plattformversionen verwenden unterschiedliche Versionen von SQLite. Wenn Sie dieselbe Engine wie auf einem Android-Gerät nutzen möchten, nutzen Sie adb shell
und führen Sie sqlite3
auf Ihrem Zielgerät aus.
Sie können SQLite zur Zeitabfrage verwenden:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
Sie können SQLite bitten, mit EXPLAIN QUERY PLAN
zu erklären, wie eine Abfrage beantwortet werden soll:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
Im vorherigen Beispiel ist ein vollständiger Tabellenscan ohne Index erforderlich, um alle Kunden aus Paris zu finden. Dies wird als lineare Komplexität bezeichnet. SQLite muss alle Zeilen lesen und nur die Zeilen beibehalten, die mit Kunden aus Paris übereinstimmen. Um dies zu beheben, können Sie einen Index hinzufügen:
sqlite> CREATE INDEX Idx1 ON Customers(city);
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SEARCH test USING INDEX Idx1 (city=?
Wenn Sie die interaktive Shell verwenden, können Sie SQLite bitten, immer Abfragepläne zu erklären:
sqlite> .eqp on
Weitere Informationen finden Sie unter Abfrageplanung.
SQLite-Analysetool
SQLite bietet die sqlite3_analyzer
-Befehlszeile zum Dump von zusätzlichen Informationen, die zur Fehlerbehebung bei der Leistung verwendet werden können. Rufen Sie zur Installation die SQLite-Downloadseite auf.
Mit adb pull
können Sie eine Datenbankdatei von einem Zielgerät zur Analyse auf Ihre Workstation herunterladen:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite-Browser
Sie können das GUI-Tool SQLite-Browser auch über die SQLite-Downloadseite installieren.
Android-Protokollierung
Android verwendet SQLite-Abfragen und protokolliert sie für dich:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
```### Perfetto tracing
### Perfetto tracing {:#perfetto-tracing}
When [configuring Perfetto](https://perfetto.dev/docs/concepts/config), you may
add the following to include tracks for individual queries:
```protobuf
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Benchmarks in Continuous Integration ausführen
- Eingefrorene Frames
- Baseline-Profile ohne MacroBenchmark erstellen und messen