ART: Add very-large threshold to dex2oat
Add a variable threshold to dex2oat. If the total dex file size for
an app reaches this threshold, dex2oat will punt all compilation and
compile the app with verify-at-runtime. This ensures smaller compile
time and memory thrashing, while still extracting the dex files and
thus helping with dirty memory later.
Added tests.
Bug: 29557002
Bug: 29790079
Test: m test-art-host-gtest-dex2oat_test
Change-Id: I78870e4a80ccaafcbbe56839e61ced0acd2ca05e
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 13d1d0b..cfb905b 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -22,6 +22,7 @@
#include <fstream>
#include <iostream>
+#include <limits>
#include <sstream>
#include <string>
#include <unordered_set>
@@ -364,6 +365,10 @@
UsageError(" Example: --swap-dex-count-threshold=10");
UsageError(" Default: %zu", kDefaultMinDexFilesForSwap);
UsageError("");
+ UsageError(" --very-large-app-threshold=<size>: specifies the minimum total dex file size in");
+ UsageError(" bytes to consider the input \"very large\" and punt on the compilation.");
+ UsageError(" Example: --very-large-app-threshold=100000000");
+ UsageError("");
UsageError(" --app-image-fd=<file-descriptor>: specify output file descriptor for app image.");
UsageError(" Example: --app-image-fd=10");
UsageError("");
@@ -1142,6 +1147,11 @@
"--swap-dex-count-threshold",
&min_dex_files_for_swap_,
Usage);
+ } else if (option.starts_with("--very-large-app-threshold=")) {
+ ParseUintOption(option,
+ "--very-large-app-threshold",
+ &very_large_threshold_,
+ Usage);
} else if (option.starts_with("--app-image-file=")) {
app_image_file_name_ = option.substr(strlen("--app-image-file=")).data();
} else if (option.starts_with("--app-image-fd=")) {
@@ -1424,6 +1434,19 @@
}
// Note that dex2oat won't close the swap_fd_. The compiler driver's swap space will do that.
+ // If we need to downgrade the compiler-filter for size reasons, do that check now.
+ if (!IsBootImage() && IsVeryLarge(dex_files_)) {
+ if (!CompilerFilter::IsAsGoodAs(CompilerFilter::kVerifyAtRuntime,
+ compiler_options_->GetCompilerFilter())) {
+ LOG(INFO) << "Very large app, downgrading to verify-at-runtime.";
+ // Note: this change won't be reflected in the key-value store, as that had to be
+ // finalized before loading the dex files. This setup is currently required
+ // to get the size from the DexFile objects.
+ // TODO: refactor. b/29790079
+ compiler_options_->SetCompilerFilter(CompilerFilter::kVerifyAtRuntime);
+ }
+ }
+
if (IsBootImage()) {
// For boot image, pass opened dex files to the Runtime::Create().
// Note: Runtime acquires ownership of these dex files.
@@ -1919,6 +1942,14 @@
return dex_files_size >= min_dex_file_cumulative_size_for_swap_;
}
+ bool IsVeryLarge(std::vector<const DexFile*>& dex_files) {
+ size_t dex_files_size = 0;
+ for (const auto* dex_file : dex_files) {
+ dex_files_size += dex_file->GetHeader().file_size_;
+ }
+ return dex_files_size >= very_large_threshold_;
+ }
+
template <typename T>
static std::vector<T*> MakeNonOwningPointerVector(const std::vector<std::unique_ptr<T>>& src) {
std::vector<T*> result;
@@ -2508,6 +2539,7 @@
int swap_fd_;
size_t min_dex_files_for_swap_ = kDefaultMinDexFilesForSwap;
size_t min_dex_file_cumulative_size_for_swap_ = kDefaultMinDexFileCumulativeSizeForSwap;
+ size_t very_large_threshold_ = std::numeric_limits<size_t>::max();
std::string app_image_file_name_;
int app_image_fd_;
std::string profile_file_;
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index de3aed9..6188883 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -24,6 +24,8 @@
#include "base/macros.h"
#include "base/stringprintf.h"
#include "dex2oat_environment_test.h"
+#include "oat.h"
+#include "oat_file.h"
#include "utils.h"
#include <sys/wait.h>
@@ -284,4 +286,134 @@
{ "--swap-dex-size-threshold=0", "--swap-dex-count-threshold=0" });
}
+class Dex2oatVeryLargeTest : public Dex2oatTest {
+ protected:
+ void CheckFilter(CompilerFilter::Filter input ATTRIBUTE_UNUSED,
+ CompilerFilter::Filter result ATTRIBUTE_UNUSED) OVERRIDE {
+ // Ignore, we'll do our own checks.
+ }
+
+ void RunTest(CompilerFilter::Filter filter,
+ bool expect_large,
+ const std::vector<std::string>& extra_args = {}) {
+ std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
+ std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
+
+ Copy(GetDexSrc1(), dex_location);
+
+ std::vector<std::string> copy(extra_args);
+
+ GenerateOdexForTest(dex_location, odex_location, filter, copy);
+
+ CheckValidity();
+ ASSERT_TRUE(success_);
+ CheckResult(dex_location, odex_location, filter, expect_large);
+ }
+
+ void CheckResult(const std::string& dex_location,
+ const std::string& odex_location,
+ CompilerFilter::Filter filter,
+ bool expect_large) {
+ // Host/target independent checks.
+ std::string error_msg;
+ std::unique_ptr<OatFile> odex_file(OatFile::Open(odex_location.c_str(),
+ odex_location.c_str(),
+ nullptr,
+ nullptr,
+ false,
+ /*low_4gb*/false,
+ dex_location.c_str(),
+ &error_msg));
+ ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
+ if (expect_large) {
+ // Note: we cannot check the following:
+ // EXPECT_TRUE(CompilerFilter::IsAsGoodAs(CompilerFilter::kVerifyAtRuntime,
+ // odex_file->GetCompilerFilter()));
+ // The reason is that the filter override currently happens when the dex files are
+ // loaded in dex2oat, which is after the oat file has been started. Thus, the header
+ // store cannot be changed, and the original filter is set in stone.
+
+ for (const OatDexFile* oat_dex_file : odex_file->GetOatDexFiles()) {
+ std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
+ ASSERT_TRUE(dex_file != nullptr);
+ uint32_t class_def_count = dex_file->NumClassDefs();
+ ASSERT_LT(class_def_count, std::numeric_limits<uint16_t>::max());
+ for (uint16_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) {
+ OatFile::OatClass oat_class = oat_dex_file->GetOatClass(class_def_index);
+ EXPECT_EQ(oat_class.GetType(), OatClassType::kOatClassNoneCompiled);
+ }
+ }
+
+ // If the input filter was "below," it should have been used.
+ if (!CompilerFilter::IsAsGoodAs(CompilerFilter::kVerifyAtRuntime, filter)) {
+ EXPECT_EQ(odex_file->GetCompilerFilter(), filter);
+ }
+ } else {
+ EXPECT_EQ(odex_file->GetCompilerFilter(), filter);
+ }
+
+ // Host/target dependent checks.
+ if (kIsTargetBuild) {
+ CheckTargetResult(expect_large);
+ } else {
+ CheckHostResult(expect_large);
+ }
+ }
+
+ void CheckTargetResult(bool expect_large ATTRIBUTE_UNUSED) {
+ // TODO: Ignore for now. May do something for fd things.
+ }
+
+ void CheckHostResult(bool expect_large) {
+ if (!kIsTargetBuild) {
+ if (expect_large) {
+ EXPECT_NE(output_.find("Very large app, downgrading to verify-at-runtime."),
+ std::string::npos)
+ << output_;
+ } else {
+ EXPECT_EQ(output_.find("Very large app, downgrading to verify-at-runtime."),
+ std::string::npos)
+ << output_;
+ }
+ }
+ }
+
+ // Check whether the dex2oat run was really successful.
+ void CheckValidity() {
+ if (kIsTargetBuild) {
+ CheckTargetValidity();
+ } else {
+ CheckHostValidity();
+ }
+ }
+
+ void CheckTargetValidity() {
+ // TODO: Ignore for now.
+ }
+
+ // On the host, we can get the dex2oat output. Here, look for "dex2oat took."
+ void CheckHostValidity() {
+ EXPECT_NE(output_.find("dex2oat took"), std::string::npos) << output_;
+ }
+};
+
+TEST_F(Dex2oatVeryLargeTest, DontUseVeryLarge) {
+ RunTest(CompilerFilter::kVerifyNone, false);
+ RunTest(CompilerFilter::kVerifyAtRuntime, false);
+ RunTest(CompilerFilter::kInterpretOnly, false);
+ RunTest(CompilerFilter::kSpeed, false);
+
+ RunTest(CompilerFilter::kVerifyNone, false, { "--very-large-app-threshold=1000000" });
+ RunTest(CompilerFilter::kVerifyAtRuntime, false, { "--very-large-app-threshold=1000000" });
+ RunTest(CompilerFilter::kInterpretOnly, false, { "--very-large-app-threshold=1000000" });
+ RunTest(CompilerFilter::kSpeed, false, { "--very-large-app-threshold=1000000" });
+}
+
+TEST_F(Dex2oatVeryLargeTest, UseVeryLarge) {
+ RunTest(CompilerFilter::kVerifyNone, false, { "--very-large-app-threshold=100" });
+ RunTest(CompilerFilter::kVerifyAtRuntime, false, { "--very-large-app-threshold=100" });
+ RunTest(CompilerFilter::kInterpretOnly, true, { "--very-large-app-threshold=100" });
+ RunTest(CompilerFilter::kSpeed, true, { "--very-large-app-threshold=100" });
+}
+
} // namespace art