service/method-outliner/OutliningProfileGuidanceImpl.cpp (215 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "OutliningProfileGuidanceImpl.h"
#include "ConfigFiles.h"
#include "MethodProfiles.h"
#include "PassManager.h"
#include "SourceBlocks.h"
#include "Walkers.h"
namespace outliner_impl {
using namespace outliner;
void gather_sufficiently_warm_and_hot_methods(
const Scope& scope,
ConfigFiles& config_files,
PassManager& mgr,
const ProfileGuidanceConfig& config,
std::unordered_set<DexMethod*>* sufficiently_warm_methods,
std::unordered_set<DexMethod*>* sufficiently_hot_methods) {
bool has_method_profiles{false};
if (config.use_method_profiles) {
auto& method_profiles = config_files.get_method_profiles();
if (method_profiles.has_stats()) {
has_method_profiles = true;
for (auto& p : method_profiles.all_interactions()) {
auto& method_stats = p.second;
walk::methods(scope,
[sufficiently_warm_methods,
sufficiently_hot_methods,
&method_stats,
&config](DexMethod* method) {
auto it = method_stats.find(method);
if (it == method_stats.end()) {
return;
}
if (it->second.appear_percent >=
config.method_profiles_appear_percent) {
if (it->second.call_count >
config.method_profiles_hot_call_count) {
sufficiently_hot_methods->insert(method);
} else if (it->second.call_count >=
config.method_profiles_warm_call_count) {
sufficiently_warm_methods->insert(method);
}
}
});
}
}
}
std::unordered_set<DexType*> perf_sensitive_classes;
if (mgr.interdex_has_run()) {
walk::classes(scope, [&perf_sensitive_classes](DexClass* cls) {
if (cls->is_perf_sensitive()) {
perf_sensitive_classes.insert(cls->get_type());
}
});
} else {
for (const auto& str : config_files.get_coldstart_classes()) {
DexType* type = DexType::get_type(str);
if (type) {
perf_sensitive_classes.insert(type);
}
}
}
switch (config.perf_sensitivity) {
case PerfSensitivity::kNeverUse:
break;
case PerfSensitivity::kWarmWhenNoProfiles:
if (has_method_profiles) {
break;
}
FALLTHROUGH_INTENDED;
case PerfSensitivity::kAlwaysWarm:
walk::methods(scope,
[sufficiently_warm_methods,
&perf_sensitive_classes](DexMethod* method) {
if (perf_sensitive_classes.count(method->get_class())) {
sufficiently_warm_methods->insert(method);
}
});
break;
case PerfSensitivity::kHotWhenNoProfiles:
if (has_method_profiles) {
break;
}
FALLTHROUGH_INTENDED;
case PerfSensitivity::kAlwaysHot:
walk::methods(
scope,
[sufficiently_hot_methods, &perf_sensitive_classes](DexMethod* method) {
if (perf_sensitive_classes.count(method->get_class())) {
sufficiently_hot_methods->insert(method);
}
});
break;
}
}
PerfSensitivity parse_perf_sensitivity(const std::string& str) {
if (str == "never") {
return PerfSensitivity::kNeverUse;
}
if (str == "warm-when-no-profiles") {
return PerfSensitivity::kWarmWhenNoProfiles;
}
if (str == "always-warm") {
return PerfSensitivity::kAlwaysWarm;
}
if (str == "hot-when-no-profiles") {
return PerfSensitivity::kHotWhenNoProfiles;
}
if (str == "always-hot") {
return PerfSensitivity::kAlwaysHot;
}
always_assert_log(false, "Unknown perf sensitivity: %s", str.c_str());
}
CanOutlineBlockDecider::CanOutlineBlockDecider(
const outliner::ProfileGuidanceConfig& config,
bool sufficiently_warm,
bool sufficiently_hot)
: m_config(config),
m_sufficiently_warm(sufficiently_warm),
m_sufficiently_hot(sufficiently_hot) {}
CanOutlineBlockDecider::Result
CanOutlineBlockDecider::can_outline_from_big_block(
const big_blocks::BigBlock& big_block) const {
if (!m_sufficiently_hot && !m_sufficiently_warm) {
return Result::CanOutline;
}
if (!m_sufficiently_hot) {
always_assert(m_sufficiently_warm);
// Make sure m_is_in_loop is initialized
if (!m_is_in_loop) {
m_is_in_loop.reset(
new LazyUnorderedMap<cfg::Block*, bool>([](cfg::Block* block) {
std::unordered_set<cfg::Block*> visited;
std::queue<cfg::Block*> work_queue;
for (auto e : block->succs()) {
work_queue.push(e->target());
}
while (!work_queue.empty()) {
auto other_block = work_queue.front();
work_queue.pop();
if (visited.insert(other_block).second) {
if (block == other_block) {
return true;
}
for (auto e : other_block->succs()) {
work_queue.push(e->target());
}
}
}
return false;
}));
}
if (!(*m_is_in_loop)[big_block.get_first_block()]) {
return Result::CanOutline;
}
}
// If we get here,
// - the method is hot, or
// - the method is not hot but warm, and the big block is in a loop
if (m_config.block_profiles_hits < 0) {
return m_sufficiently_hot ? Result::Hot : Result::WarmLoop;
}
// Make sure m_max_vals is initialized
if (!m_max_vals) {
m_max_vals.reset(new LazyUnorderedMap<cfg::Block*, boost::optional<float>>(
[](cfg::Block* block) -> boost::optional<float> {
auto* sb = source_blocks::get_first_source_block(block);
if (sb == nullptr) {
return boost::none;
}
boost::optional<float> max_val;
sb->foreach_val([&](const auto& val_pair) {
if (!val_pair) {
return;
}
if (!max_val || (val_pair && val_pair->val > *max_val)) {
max_val = val_pair->val;
}
});
return max_val;
}));
}
// Via m_max_vals, we consider the maximum hit number for each block.
// Across all blocks, we are gathering the *minimum* of those hit numbers.
boost::optional<float> min_val;
for (auto block : big_block.get_blocks()) {
auto val = (*m_max_vals)[block];
if (!min_val || (val && *val < *min_val)) {
min_val = val;
if (min_val && *min_val == 0) {
break;
}
}
}
// Let's also look back at dominators. It's beneficial if we can tighten the
// minimum.
auto block = big_block.get_first_block();
auto& cfg = block->cfg();
auto entry_block = cfg.entry_block();
if (block != entry_block && (!min_val || *min_val != 0)) {
if (!m_dominators) {
m_dominators.reset(
new dominators::SimpleFastDominators<cfg::GraphInterface>(cfg));
}
do {
block = m_dominators->get_idom(block);
auto val = (*m_max_vals)[block];
if (!min_val || (val && *val < *min_val)) {
min_val = val;
if (min_val && *min_val == 0) {
break;
}
}
} while (block != entry_block);
}
if (!min_val) {
return m_sufficiently_hot ? Result::HotNoSourceBlocks
: Result::WarmLoopNoSourceBlocks;
}
if (*min_val > m_config.block_profiles_hits) {
return m_sufficiently_hot ? Result::HotExceedsThresholds
: Result::WarmLoopExceedsThresholds;
}
return Result::CanOutline;
}
} // namespace outliner_impl