From 6f4fa68f0d588156f776e3923d3fde280008ed0b Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 16 Jun 2026 09:36:18 +0800 Subject: [PATCH] =?UTF-8?q?build(benchmark):=20=E5=90=8C=E6=97=B6=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=20Baseline=20Profile=20=E4=B8=8E=20Startup=20Profile?= =?UTF-8?q?=20=E5=88=B0=20:core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macrobenchmark/build.gradle.kts | 86 ++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/macrobenchmark/build.gradle.kts b/macrobenchmark/build.gradle.kts index d1a9d0e..1ccd648 100644 --- a/macrobenchmark/build.gradle.kts +++ b/macrobenchmark/build.gradle.kts @@ -42,53 +42,83 @@ dependencies { implementation(libs.androidx.test.uiautomator) } -// ===== Baseline Profile 自动复制 ===== +// ===== Baseline Profile / Startup Profile 自动复制 ===== // 运行 ./gradlew :macrobenchmark:updateBaselineProfile 即可一键生成并复制 val updateBaselineProfile by tasks.registering { group = "benchmark" - description = "运行 connectedBenchmarkAndroidTest 并将生成的 startup-prof.txt 复制到 :core 模块" + description = "运行 connectedBenchmarkAndroidTest 并将生成的 Baseline Profile 与 Startup Profile 复制到 :core 模块" // 依赖基准测试 task(需要先连接设备/模拟器) dependsOn("connectedBenchmarkAndroidTest") + // 该 task 的输入是 benchmark 运行后的产物,因此不应被跳过 + outputs.upToDateWhen { false } + // 预先计算目标路径,避免在 doLast 中引用 project 对象(configuration cache 兼容) - val targetPath = rootProject.projectDir.resolve("core/src/main/baseline-prof.txt").absolutePath + val baselineTargetPath = rootProject.projectDir + .resolve("core/src/main/baseline-prof.txt") + .absolutePath + val startupTargetDirPath = rootProject.projectDir + .resolve("core/src/main/baselineProfiles") + .absolutePath val buildDirPath = layout.buildDirectory.get().asFile.absolutePath doLast { - // 寻找生成的 profile 文件(benchmark 1.4+ 文件名格式:{Class}_{Method}-startup-prof.txt) val sourcePaths = listOf( "$buildDirPath/outputs/connected_android_test_additional_output/benchmark/", "$buildDirPath/outputs/connected_android_test_additional_output/", ) - val targetFile = File(targetPath) - var copied = false - for (path in sourcePaths) { - val dir = File(path) - if (!dir.exists()) continue - - // 优先匹配不带时间戳的 startup-prof.txt(benchmark 1.4+ 格式) - val profileFile = dir.walkTopDown() - .firstOrNull { it.name.endsWith("-startup-prof.txt") && !it.name.contains(Regex("-\\d{4}-\\d{2}-\\d{2}-")) } - ?: continue - - profileFile.copyTo(targetFile, overwrite = true) - println("✅ Baseline Profile 已自动复制到: ${targetFile.absolutePath}") - println(" 来源: ${profileFile.absolutePath}") - copied = true - break + // benchmark 1.4+ 文件名格式:{Class}_{Method}-baseline-prof.txt / {Class}_{Method}-startup-prof.txt + // 排除带时间戳的历史文件(如 ...-2026-06-15-startup-prof.txt),只取当前 canonical 文件 + val dateStampRegex = Regex("-\\d{4}-\\d{2}-\\d{2}-") + fun findNewestProfile(suffix: String): File? { + val candidates = sourcePaths.flatMap { path -> + val dir = File(path) + if (!dir.exists()) emptyList() + else dir.walkTopDown().filter { + it.isFile && + it.name.endsWith(suffix) && + !it.name.contains(dateStampRegex) + }.toList() + } + return candidates + .sortedWith(compareByDescending { it.lastModified() }.thenBy { it.absolutePath }) + .firstOrNull() } - if (!copied) { - throw GradleException( - "未找到生成的 *-startup-prof.txt。\n" + - "请确认:\n" + - " 1. 设备/模拟器已连接 (adb devices)\n" + - " 2. 应用已安装在 benchmark 构建类型下\n" + - " 3. 检查 macrobenchmark/build/outputs/ 下是否有输出" - ) + fun requireProfile(suffix: String, label: String): File { + val sourceFile = findNewestProfile(suffix) + ?: throw GradleException( + "未找到生成的 *$suffix。\n" + + "请确认:\n" + + " 1. 设备/模拟器已连接 (adb devices)\n" + + " 2. 应用已安装在 benchmark 构建类型下\n" + + " 3. 检查 macrobenchmark/build/outputs/ 下是否有输出" + ) + if (sourceFile.length() == 0L) { + throw GradleException("生成的 $label 文件为空: ${sourceFile.absolutePath}") + } + return sourceFile } + + // 1) 先定位并校验两份源文件,确保都可用再开始复制,避免部分覆盖目标文件 + val baselineSource = requireProfile("-baseline-prof.txt", "Baseline Profile") + val startupSource = requireProfile("-startup-prof.txt", "Startup Profile") + + // 2) Baseline Profile → AOT 编译优化 + val baselineTargetFile = File(baselineTargetPath) + baselineSource.copyTo(baselineTargetFile, overwrite = true) + logger.lifecycle("✅ Baseline Profile 已自动复制到: ${baselineTargetFile.absolutePath}") + logger.lifecycle(" 来源: ${baselineSource.absolutePath}") + + // 3) Startup Profile → DEX layout 优化(必须放在 baselineProfiles/startup-prof.txt) + val startupTargetDir = File(startupTargetDirPath) + startupTargetDir.mkdirs() + val startupTargetFile = File(startupTargetDir, "startup-prof.txt") + startupSource.copyTo(startupTargetFile, overwrite = true) + logger.lifecycle("✅ Startup Profile 已自动复制到: ${startupTargetFile.absolutePath}") + logger.lifecycle(" 来源: ${startupSource.absolutePath}") } }