Reland: [Impeller] add a configuration option that allows defering all PSO construction until needed. (#165622)

The cost of bootstapping the initial PSOs can regress cold startup time
for customer money. As an experiment, attempt to defer PSO construction
to skia like.

---------

Co-authored-by: Aaron Clarke <aaclarke@google.com>
Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
This commit is contained in:
Jonah Williams
2025-03-21 18:34:05 -07:00
committed by GitHub
parent a4b982738e
commit 31ff6497f1
50 changed files with 424 additions and 211 deletions

View File

@@ -0,0 +1,12 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createFlutterGalleryStartupTest(enableLazyShaderMode: true));
}

View File

@@ -270,11 +270,13 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) {
TaskFunction createFlutterGalleryStartupTest({
String target = 'lib/main.dart',
Map<String, String>? runEnvironment,
bool enableLazyShaderMode = false,
}) {
return StartupTest(
'${flutterDirectory.path}/dev/integration_tests/flutter_gallery',
target: target,
runEnvironment: runEnvironment,
enableLazyShaderMode: enableLazyShaderMode,
).run;
}
@@ -840,6 +842,17 @@ void _addVulkanGPUTracingToManifest(String testDirectory) {
_addMetadataToManifest(testDirectory, keyPairs);
}
/// Opens the file at testDirectory + 'android/app/src/main/AndroidManifest.xml'
/// <meta-data
/// android:name="io.flutter.embedding.android.ImpellerShaderMode"
/// android:value="lazy" />
void _addLazyShaderMode(String testDirectory) {
final List<(String, String)> keyPairs = <(String, String)>[
('io.flutter.embedding.android.ImpellerLazyShaderInitialization', 'true'),
];
_addMetadataToManifest(testDirectory, keyPairs);
}
/// Opens the file at testDirectory + 'android/app/src/main/AndroidManifest.xml'
/// and adds the following entry to the application.
/// <meta-data
@@ -881,10 +894,12 @@ class StartupTest {
this.reportMetrics = true,
this.target = 'lib/main.dart',
this.runEnvironment,
this.enableLazyShaderMode = false,
});
final String testDirectory;
final bool reportMetrics;
final bool enableLazyShaderMode;
final String target;
final Map<String, String>? runEnvironment;
@@ -895,144 +910,155 @@ class StartupTest {
const int iterations = 5;
final List<Map<String, dynamic>> results = <Map<String, dynamic>>[];
section('Building application');
String? applicationBinaryPath;
switch (deviceOperatingSystem) {
case DeviceOperatingSystem.android:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm,android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm64:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.fake:
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
break;
case DeviceOperatingSystem.ios:
case DeviceOperatingSystem.macos:
await flutter(
'build',
options: <String>[
if (deviceOperatingSystem == DeviceOperatingSystem.ios) 'ios' else 'macos',
'-v',
'--profile',
'--target=$target',
if (deviceOperatingSystem == DeviceOperatingSystem.ios) '--no-publish-port',
],
);
final String buildRoot = path.join(testDirectory, 'build');
applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot);
case DeviceOperatingSystem.windows:
await flutter(
'build',
options: <String>['windows', '-v', '--profile', '--target=$target'],
);
final String basename = path.basename(testDirectory);
final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64';
applicationBinaryPath = path.join(
testDirectory,
'build',
'windows',
arch,
'runner',
'Profile',
'$basename.exe',
);
if (enableLazyShaderMode) {
_addLazyShaderMode(testDirectory);
}
const int maxFailures = 3;
int currentFailures = 0;
for (int i = 0; i < iterations; i += 1) {
// Startup should not take more than a few minutes. After 10 minutes,
// take a screenshot to help debug.
final Timer timer = Timer(const Duration(minutes: 10), () async {
print('Startup not completed within 10 minutes. Taking a screenshot...');
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
);
});
final int result = await flutter(
'run',
options: <String>[
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
'--profile',
'--trace-startup',
'--target=$target',
'-d',
device.deviceId,
if (applicationBinaryPath != null) '--use-application-binary=$applicationBinaryPath',
],
environment: runEnvironment,
canFail: true,
);
timer.cancel();
if (result == 0) {
final Map<String, dynamic> data =
json.decode(
file(
'${testOutputDirectory(testDirectory)}/start_up_info.json',
).readAsStringSync(),
)
as Map<String, dynamic>;
results.add(data);
} else {
currentFailures += 1;
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_failure_$currentFailures.png',
);
i -= 1;
if (currentFailures == maxFailures) {
return TaskResult.failure('Application failed to start $maxFailures times');
}
try {
section('Building application');
String? applicationBinaryPath;
switch (deviceOperatingSystem) {
case DeviceOperatingSystem.android:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm,android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.androidArm64:
await flutter(
'build',
options: <String>[
'apk',
'-v',
'--profile',
'--target-platform=android-arm64',
'--target=$target',
],
);
applicationBinaryPath = '$testDirectory/build/app/outputs/flutter-apk/app-profile.apk';
case DeviceOperatingSystem.fake:
case DeviceOperatingSystem.fuchsia:
case DeviceOperatingSystem.linux:
break;
case DeviceOperatingSystem.ios:
case DeviceOperatingSystem.macos:
await flutter(
'build',
options: <String>[
if (deviceOperatingSystem == DeviceOperatingSystem.ios) 'ios' else 'macos',
'-v',
'--profile',
'--target=$target',
if (deviceOperatingSystem == DeviceOperatingSystem.ios) '--no-publish-port',
],
);
final String buildRoot = path.join(testDirectory, 'build');
applicationBinaryPath = _findDarwinAppInBuildDirectory(buildRoot);
case DeviceOperatingSystem.windows:
await flutter(
'build',
options: <String>['windows', '-v', '--profile', '--target=$target'],
);
final String basename = path.basename(testDirectory);
final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64';
applicationBinaryPath = path.join(
testDirectory,
'build',
'windows',
arch,
'runner',
'Profile',
'$basename.exe',
);
}
await device.uninstallApp();
const int maxFailures = 3;
int currentFailures = 0;
for (int i = 0; i < iterations; i += 1) {
// Startup should not take more than a few minutes. After 10 minutes,
// take a screenshot to help debug.
final Timer timer = Timer(const Duration(minutes: 10), () async {
print('Startup not completed within 10 minutes. Taking a screenshot...');
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
);
});
final int result = await flutter(
'run',
options: <String>[
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
'--profile',
'--trace-startup',
'--target=$target',
'-d',
device.deviceId,
if (applicationBinaryPath != null) '--use-application-binary=$applicationBinaryPath',
],
environment: runEnvironment,
canFail: true,
);
timer.cancel();
if (result == 0) {
final Map<String, dynamic> data =
json.decode(
file(
'${testOutputDirectory(testDirectory)}/start_up_info.json',
).readAsStringSync(),
)
as Map<String, dynamic>;
results.add(data);
} else {
currentFailures += 1;
await _flutterScreenshot(
device.deviceId,
'screenshot_startup_failure_$currentFailures.png',
);
i -= 1;
if (currentFailures == maxFailures) {
return TaskResult.failure('Application failed to start $maxFailures times');
}
}
await device.uninstallApp();
}
final Map<String, dynamic> averageResults = _average(results, iterations);
if (!reportMetrics) {
return TaskResult.success(averageResults);
}
return TaskResult.success(
averageResults,
benchmarkScoreKeys: <String>[
'timeToFirstFrameMicros',
'timeToFirstFrameRasterizedMicros',
],
);
} finally {
await _resetManifest(testDirectory);
}
final Map<String, dynamic> averageResults = _average(results, iterations);
if (!reportMetrics) {
return TaskResult.success(averageResults);
}
return TaskResult.success(
averageResults,
benchmarkScoreKeys: <String>['timeToFirstFrameMicros', 'timeToFirstFrameRasterizedMicros'],
);
});
}
@@ -1180,6 +1206,7 @@ class PerfTest {
this.disablePartialRepaint = false,
this.enableMergedPlatformThread = false,
this.enableSurfaceControl = false,
this.enableLazyShaderMode = false,
this.createPlatforms = const <String>[],
}) : _resultFilename = resultFilename;
@@ -1202,6 +1229,7 @@ class PerfTest {
this.disablePartialRepaint = false,
this.enableMergedPlatformThread = false,
this.enableSurfaceControl = false,
this.enableLazyShaderMode = false,
this.createPlatforms = const <String>[],
}) : saveTraceFile = false,
timelineFileName = null,
@@ -1261,6 +1289,9 @@ class PerfTest {
/// Whether to enable SurfaceControl swapchain.
final bool enableSurfaceControl;
/// Whether to defer construction of all PSO objects in the Impeller backend.
final bool enableLazyShaderMode;
/// Number of seconds to time out the test after, allowing debug callbacks to run.
final int? timeoutSeconds;
@@ -1359,6 +1390,9 @@ class PerfTest {
if (enableSurfaceControl) {
_addSurfaceControlSupportToManifest(testDirectory);
}
if (enableLazyShaderMode) {
_addLazyShaderMode(testDirectory);
}
}
if (disablePartialRepaint || enableMergedPlatformThread) {
changedPlist = true;