Be more prescise in the profile analysis
Distinguish between:
- compile
- don't compile because we don't have enough delta (standard case)
- don't compile because all profiles are empty
This will help us be more precise in the compilation strategy and
avoid re-compiling verified dex files when the profile is empty.
Also, this CL fixes dexoptanalyzer tests.
We needed to generate odex files instead of oat files in the
dalvik-cache when testing the functionality. That's because during
tests, the parent directory of the apk is always writable and
OatFileAssistant will believe that it needs to select the odex
files despite having an oat file.
The fix caught issues in the downgrade test, which were also
addressed.
Test: gtest
Bug: 188655918
Change-Id: Id8370541f73465b32dc91aeacf2ba4dc2656c290
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 92baff7..99e8286 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -87,8 +87,8 @@
UsageError(" --compiler-filter=<string>: the target compiler filter to be used as reference");
UsageError(" when deciding if the dex file needs to be optimized.");
UsageError("");
- UsageError(" --assume-profile-changed: assumes the profile information has changed");
- UsageError(" when deciding if the dex file needs to be optimized.");
+ UsageError(" --profile_analysis_result=<int>: the result of the profile analysis, used in");
+ UsageError(" deciding if the dex file needs to be optimized.");
UsageError("");
UsageError(" --image=<filename>: optional, the image to be used to decide if the associated");
UsageError(" oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art.");
@@ -150,7 +150,6 @@
DexoptAnalyzer() :
only_flatten_context_(false),
only_validate_bcp_(false),
- assume_profile_changed_(false),
downgrade_(false) {}
void ParseArgs(int argc, char **argv) {
@@ -170,8 +169,16 @@
for (int i = 0; i < argc; ++i) {
const char* raw_option = argv[i];
const std::string_view option(raw_option);
- if (option == "--assume-profile-changed") {
- assume_profile_changed_ = true;
+
+ if (StartsWith(option, "--profile-analysis-result=")) {
+ int parse_result = std::stoi(std::string(
+ option.substr(strlen("--profile-analysis-result="))), nullptr, 0);
+ if (parse_result != static_cast<int>(ProfileAnalysisResult::kOptimize) &&
+ parse_result != static_cast<int>(ProfileAnalysisResult::kDontOptimizeSmallDelta) &&
+ parse_result != static_cast<int>(ProfileAnalysisResult::kDontOptimizeEmptyProfiles)) {
+ Usage("Invalid --profile-analysis-result= %d", parse_result);
+ }
+ profile_analysis_result_ = static_cast<ProfileAnalysisResult>(parse_result);
} else if (StartsWith(option, "--dex-file=")) {
dex_file_ = std::string(option.substr(strlen("--dex-file=")));
} else if (StartsWith(option, "--compiler-filter=")) {
@@ -326,8 +333,18 @@
return ReturnCode::kNoDexOptNeeded;
}
- int dexoptNeeded = oat_file_assistant->GetDexOptNeeded(compiler_filter_,
- assume_profile_changed_,
+ // If the compiler filter depends on profiles but the profiles are empty,
+ // change the test filter to kVerify. It's what dex2oat also does.
+ CompilerFilter::Filter actual_compiler_filter = compiler_filter_;
+ if (CompilerFilter::DependsOnProfile(compiler_filter_) &&
+ profile_analysis_result_ == ProfileAnalysisResult::kDontOptimizeEmptyProfiles) {
+ actual_compiler_filter = CompilerFilter::kVerify;
+ }
+
+ // TODO: GetDexOptNeeded should get the raw analysis result instead of assume_profile_changed.
+ bool assume_profile_changed = profile_analysis_result_ == ProfileAnalysisResult::kOptimize;
+ int dexoptNeeded = oat_file_assistant->GetDexOptNeeded(actual_compiler_filter,
+ assume_profile_changed,
downgrade_);
// Convert OatFileAssistant codes to dexoptanalyzer codes.
@@ -453,7 +470,7 @@
std::string context_str_;
bool only_flatten_context_;
bool only_validate_bcp_;
- bool assume_profile_changed_;
+ ProfileAnalysisResult profile_analysis_result_;
bool downgrade_;
std::string image_;
std::vector<const char*> runtime_args_;
diff --git a/dexoptanalyzer/dexoptanalyzer.h b/dexoptanalyzer/dexoptanalyzer.h
index 3ecff2f..8083057 100644
--- a/dexoptanalyzer/dexoptanalyzer.h
+++ b/dexoptanalyzer/dexoptanalyzer.h
@@ -39,6 +39,13 @@
kErrorUnknownDexOptNeeded = 103
};
+// Accepted values for the profile analysis results.
+enum class ProfileAnalysisResult {
+ kOptimize = 1,
+ kDontOptimizeSmallDelta = 2,
+ kDontOptimizeEmptyProfiles = 3,
+};
+
} // namespace dexoptanalyzer
} // namespace art
diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc
index ef6282a..7abd4ba 100644
--- a/dexoptanalyzer/dexoptanalyzer_test.cc
+++ b/dexoptanalyzer/dexoptanalyzer_test.cc
@@ -16,11 +16,15 @@
#include <gtest/gtest.h>
+#include <string>
+
#include "arch/instruction_set.h"
#include "base/compiler_filter.h"
#include "dexopt_test.h"
+#include "dexoptanalyzer.h"
namespace art {
+namespace dexoptanalyzer {
class DexoptAnalyzerTest : public DexoptTest {
protected:
@@ -35,17 +39,21 @@
int Analyze(const std::string& dex_file,
CompilerFilter::Filter compiler_filter,
- bool assume_profile_changed,
- const char* class_loader_context) {
+ ProfileAnalysisResult profile_analysis_result,
+ const char* class_loader_context,
+ bool downgrade = false) {
std::string dexoptanalyzer_cmd = GetDexoptAnalyzerCmd();
std::vector<std::string> argv_str;
argv_str.push_back(dexoptanalyzer_cmd);
argv_str.push_back("--dex-file=" + dex_file);
argv_str.push_back("--isa=" + std::string(GetInstructionSetString(kRuntimeISA)));
argv_str.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(compiler_filter));
- if (assume_profile_changed) {
- argv_str.push_back("--assume-profile-changed");
+ argv_str.push_back("--profile-analysis-result=" +
+ std::to_string(static_cast<int>(profile_analysis_result)));
+ if (downgrade) {
+ argv_str.push_back("--downgrade");
}
+
argv_str.push_back("--runtime-arg");
argv_str.push_back(GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames()));
argv_str.push_back("--runtime-arg");
@@ -76,7 +84,8 @@
// as the output of OatFileAssistant::GetDexOptNeeded.
void Verify(const std::string& dex_file,
CompilerFilter::Filter compiler_filter,
- bool assume_profile_changed = false,
+ ProfileAnalysisResult profile_analysis_result =
+ ProfileAnalysisResult::kDontOptimizeSmallDelta,
bool downgrade = false,
const char* class_loader_context = "PCL[]") {
std::unique_ptr<ClassLoaderContext> context = class_loader_context == nullptr
@@ -88,12 +97,13 @@
}
int dexoptanalyzerResult = Analyze(
- dex_file, compiler_filter, assume_profile_changed, class_loader_context);
+ dex_file, compiler_filter, profile_analysis_result, class_loader_context, downgrade);
dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult);
OatFileAssistant oat_file_assistant(dex_file.c_str(),
kRuntimeISA,
context.get(),
/*load_executable=*/ false);
+ bool assume_profile_changed = profile_analysis_result == ProfileAnalysisResult::kOptimize;
int assistantResult = oat_file_assistant.GetDexOptNeeded(
compiler_filter, assume_profile_changed, downgrade);
EXPECT_EQ(assistantResult, dexoptanalyzerResult);
@@ -102,7 +112,7 @@
// The tests below exercise the same test case from oat_file_assistant_test.cc.
-// Case: We have a DEX file, but no OAT file for it.
+// Case: We have a DEX file, but no ODEX file for it.
TEST_F(DexoptAnalyzerTest, DexNoOat) {
std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
Copy(GetDexSrc1(), dex_location);
@@ -111,91 +121,135 @@
Verify(dex_location, CompilerFilter::kExtract);
Verify(dex_location, CompilerFilter::kVerify);
Verify(dex_location, CompilerFilter::kSpeedProfile);
- Verify(dex_location, CompilerFilter::kSpeed, false, false, nullptr);
+ Verify(dex_location, CompilerFilter::kSpeed,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, false, nullptr);
}
-// Case: We have a DEX file and up-to-date OAT file for it.
+// Case: We have a DEX file and up-to-date ODEX file for it.
TEST_F(DexoptAnalyzerTest, OatUpToDate) {
std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OatUpToDate.odex";
Copy(GetDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
Verify(dex_location, CompilerFilter::kSpeed);
Verify(dex_location, CompilerFilter::kVerify);
Verify(dex_location, CompilerFilter::kExtract);
Verify(dex_location, CompilerFilter::kEverything);
- Verify(dex_location, CompilerFilter::kSpeed, false, false, nullptr);
+ Verify(dex_location, CompilerFilter::kSpeed,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, false, nullptr);
}
-// Case: We have a DEX file and speed-profile OAT file for it.
+// Case: We have a DEX file and speed-profile ODEX file for it.
TEST_F(DexoptAnalyzerTest, ProfileOatUpToDate) {
std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/ProfileOatUpToDate.odex";
Copy(GetDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeedProfile);
- Verify(dex_location, CompilerFilter::kSpeedProfile, false);
- Verify(dex_location, CompilerFilter::kVerify, false);
- Verify(dex_location, CompilerFilter::kSpeedProfile, true);
- Verify(dex_location, CompilerFilter::kVerify, true);
+ Verify(dex_location, CompilerFilter::kSpeedProfile,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta);
+ Verify(dex_location, CompilerFilter::kVerify, ProfileAnalysisResult::kDontOptimizeSmallDelta);
+ Verify(dex_location, CompilerFilter::kSpeedProfile, ProfileAnalysisResult::kOptimize);
+ Verify(dex_location, CompilerFilter::kVerify, ProfileAnalysisResult::kOptimize);
+}
+
+// Case: We have a DEX file, verify odex file for it, and we ask if it's up to date
+// when the profiles are empty or full.
+TEST_F(DexoptAnalyzerTest, VerifyAndEmptyProfiles) {
+ std::string dex_location = GetScratchDir() + "/VerifyAndEmptyProfiles.jar";
+ std::string odex_location = GetOdexDir() + "/VerifyAndEmptyProfiles.odex";
+ Copy(GetDexSrc1(), dex_location);
+
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kVerify);
+
+ // If we want to speed-profile something that was verified, do it even if
+ // the profile analysis returns kDontOptimizeSmallDelta (it means that we do have profile data,
+ // so a transition verify -> speed-profile is still worth).
+ ASSERT_EQ(
+ static_cast<int>(ReturnCode::kDex2OatForFilterOdex),
+ Analyze(dex_location, CompilerFilter::kSpeedProfile,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, "PCL[]"));
+ // If we want to speed-profile something that was verified but the profiles are empty,
+ // don't do it - there will be no gain.
+ ASSERT_EQ(
+ static_cast<int>(ReturnCode::kNoDexOptNeeded),
+ Analyze(dex_location, CompilerFilter::kSpeedProfile,
+ ProfileAnalysisResult::kDontOptimizeEmptyProfiles, "PCL[]"));
+ // Standard case where we need to re-compile a speed-profile because of sufficient new
+ // information in the profile.
+ ASSERT_EQ(
+ static_cast<int>(ReturnCode::kDex2OatForFilterOdex),
+ Analyze(dex_location, CompilerFilter::kSpeedProfile,
+ ProfileAnalysisResult::kOptimize, "PCL[]"));
}
TEST_F(DexoptAnalyzerTest, Downgrade) {
std::string dex_location = GetScratchDir() + "/Downgrade.jar";
+ std::string odex_location = GetOdexDir() + "/Downgrade.odex";
Copy(GetDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kVerify);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kVerify);
- Verify(dex_location, CompilerFilter::kSpeedProfile, false, true);
- Verify(dex_location, CompilerFilter::kVerify, false, true);
- Verify(dex_location, CompilerFilter::kExtract, false, true);
+ Verify(dex_location, CompilerFilter::kSpeedProfile,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
+ Verify(dex_location, CompilerFilter::kVerify,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
+ Verify(dex_location, CompilerFilter::kExtract,
+ ProfileAnalysisResult::kDontOptimizeSmallDelta, true);
}
-// Case: We have a MultiDEX file and up-to-date OAT file for it.
+// Case: We have a MultiDEX file and up-to-date ODEX file for it.
TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) {
std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar";
- Copy(GetMultiDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ std::string odex_location = GetOdexDir() + "/MultiDexOatUpToDate.odex";
- Verify(dex_location, CompilerFilter::kSpeed, false);
+ Copy(GetMultiDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
+
+ Verify(dex_location, CompilerFilter::kSpeed, ProfileAnalysisResult::kDontOptimizeSmallDelta);
}
// Case: We have a MultiDEX file where the secondary dex file is out of date.
TEST_F(DexoptAnalyzerTest, MultiDexSecondaryOutOfDate) {
std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar";
+ std::string odex_location = GetOdexDir() + "/MultiDexSecondaryOutOfDate.odex";
// Compile code for GetMultiDexSrc1.
Copy(GetMultiDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
// Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum
// is out of date.
Copy(GetMultiDexSrc2(), dex_location);
- Verify(dex_location, CompilerFilter::kSpeed, false);
+ Verify(dex_location, CompilerFilter::kSpeed, ProfileAnalysisResult::kDontOptimizeSmallDelta);
}
-
-// Case: We have a DEX file and an OAT file out of date with respect to the
+// Case: We have a DEX file and an ODEX file out of date with respect to the
// dex checksum.
TEST_F(DexoptAnalyzerTest, OatDexOutOfDate) {
std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar";
+ std::string odex_location = GetOdexDir() + "/OatDexOutOfDate.odex";
// We create a dex, generate an oat for it, then overwrite the dex with a
// different dex to make the oat out of date.
Copy(GetDexSrc1(), dex_location);
- GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ GenerateOdexForTest(dex_location.c_str(), odex_location.c_str(), CompilerFilter::kSpeed);
Copy(GetDexSrc2(), dex_location);
Verify(dex_location, CompilerFilter::kExtract);
Verify(dex_location, CompilerFilter::kSpeed);
}
-// Case: We have a DEX file and an OAT file out of date with respect to the
+// Case: We have a DEX file and an ODEX file out of date with respect to the
// boot image.
TEST_F(DexoptAnalyzerTest, OatImageOutOfDate) {
std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar";
+ std::string odex_location = GetOdexDir() + "/OatImageOutOfDate.odex";
Copy(GetDexSrc1(), dex_location);
GenerateOatForTest(dex_location.c_str(),
+ odex_location.c_str(),
CompilerFilter::kSpeed,
/*with_alternate_image=*/true);
@@ -210,9 +264,11 @@
// verify-at-runtime.
TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) {
std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar";
+ std::string odex_location = GetOdexDir() + "/OatVerifyAtRuntimeImageOutOfDate.odex";
Copy(GetDexSrc1(), dex_location);
GenerateOatForTest(dex_location.c_str(),
+ odex_location.c_str(),
CompilerFilter::kExtract,
/*with_alternate_image=*/true);
@@ -303,6 +359,9 @@
// Generate the odex to get the class loader context also open the dex files.
GenerateOdexForTest(dex_location1, odex_location1, CompilerFilter::kSpeed, /* compilation_reason= */ nullptr, /* extra_args= */ { class_loader_context_option });
- Verify(dex_location1, CompilerFilter::kSpeed, false, false, class_loader_context.c_str());
+ Verify(dex_location1, CompilerFilter::kSpeed, ProfileAnalysisResult::kDontOptimizeSmallDelta,
+ false, class_loader_context.c_str());
}
+
+} // namespace dexoptanalyzer
} // namespace art