Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clients/drcachesim/analyzer_multi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ analyzer_multi_tmpl_t<RecordType, ReaderType>::init_dynamic_schedule()
sched_ops.honor_direct_switches = !op_sched_disable_direct_switches.get_value();
sched_ops.exit_if_fraction_inputs_left =
op_sched_exit_if_fraction_inputs_left.get_value();
sched_ops.random_initial_layout = op_sched_random_initial_layout.get_value();
#ifdef HAS_ZIP
if (!op_record_file.get_value().empty()) {
record_schedule_zip_.reset(new zipfile_ostream_t(op_record_file.get_value()));
Expand Down
8 changes: 8 additions & 0 deletions clients/drcachesim/common/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,14 @@ droption_t<double> op_sched_exit_if_fraction_inputs_left(
"count is not considered (as it is not available), use discretion when raising "
"this value on uneven inputs.");

droption_t<int> op_sched_random_initial_layout(
DROPTION_SCOPE_FRONTEND, "sched_random_initial_layout", -1,
"Randomize initial mapping of inputs to outputs, using this seed",
"If <0, the initial assignment of inputs to outputs is done in a round-robin "
"fashion. If >=0, each input is assigned to a random output, using a seed equal to "
"this field's value (unless it is 0 in which case the current time is used). A "
"rebalance is always run after the initial layout.");

droption_t<int> op_sched_max_cores(
DROPTION_SCOPE_ALL, "sched_max_cores", 0,
"Limit scheduling to this many peak live cores",
Expand Down
1 change: 1 addition & 0 deletions clients/drcachesim/common/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ extern dynamorio::droption::droption_t<uint64_t> op_sched_migration_threshold_us
extern dynamorio::droption::droption_t<uint64_t> op_sched_rebalance_period_us;
extern dynamorio::droption::droption_t<double> op_sched_time_units_per_us;
extern dynamorio::droption::droption_t<double> op_sched_exit_if_fraction_inputs_left;
extern dynamorio::droption::droption_t<int> op_sched_random_initial_layout;
extern dynamorio::droption::droption_t<int> op_sched_max_cores;
extern dynamorio::droption::droption_t<uint64_t> op_schedule_stats_print_every;
extern dynamorio::droption::droption_t<std::string> op_syscall_template_file;
Expand Down
9 changes: 8 additions & 1 deletion clients/drcachesim/scheduler/scheduler.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2023-2025 Google, Inc. All rights reserved.
* Copyright (c) 2023-2026 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -903,6 +903,13 @@ template <typename RecordType, typename ReaderType> class scheduler_tmpl_t {
std::unique_ptr<ReaderType> kernel_syscall_reader;
/** The end reader for #kernel_syscall_reader. */
std::unique_ptr<ReaderType> kernel_syscall_reader_end;
/**
* If <0, the initial assignment of inputs to outputs is done in a round-robin
* fashion. If >=0, each input is assigned to a random output, using a seed
* equal to this field's value (unless it is 0 in which case the current time is
* used). A rebalance is always run after the initial layout.
*/
int random_initial_layout = -1;
// When adding new options, also add to print_configuration().
};

Expand Down
39 changes: 29 additions & 10 deletions clients/drcachesim/scheduler/scheduler_dynamic.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2023-2025 Google, Inc. All rights reserved.
* Copyright (c) 2023-2026 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -42,6 +42,7 @@
#include <cstdint>
#include <limits>
#include <mutex>
#include <random>
#include <sstream>
#include <thread>
#include <unordered_map>
Expand Down Expand Up @@ -111,20 +112,38 @@ scheduler_dynamic_tmpl_t<RecordType, ReaderType>::set_initial_schedule()
inputs_[i].queue_counter = i;
allq.push(&inputs_[i]);
}
// Now assign round-robin to the outputs. We have to obey bindings here: we
// just take the first. This isn't guaranteed to be perfect if there are
// many bindings (or output limits), but we run a rebalancing afterward
// (to construct it up front would take similar code to the rebalance so we
// leverage that code).
// Now assign inputs to outputs, either random or round-robin. We have to obey
// bindings here: we just take the first. This isn't guaranteed to be perfect if
// there are many bindings (or output limits), but we run a rebalancing afterward (to
// construct it up front would take similar code to the rebalance so we leverage that
// code).
if (options_.random_initial_layout >= 0) {
int rand_seed = options_.random_initial_layout;
if (rand_seed == 0) {
rand_seed = static_cast<int>(
scheduler_impl_tmpl_t<RecordType, ReaderType>::get_time_micros());
}
rand_gen_.seed(rand_seed);
}
output_ordinal_t output = 0;
while (!allq.empty()) {
input_info_t *input = allq.top();
allq.pop();
output_ordinal_t target = output;
if (!input->binding.empty())
target = *input->binding.begin();
else
output = (output + 1) % outputs_.size();
if (options_.random_initial_layout >= 0) {
if (!input->binding.empty()) {
std::vector<output_ordinal_t> bind_vec(input->binding.begin(),
input->binding.end());
size_t index = rand_gen_() % bind_vec.size();
target = bind_vec[index];
} else
target = rand_gen_() % outputs_.size();
} else {
if (!input->binding.empty())
target = *input->binding.begin();
else
output = (output + 1) % outputs_.size();
}
add_to_ready_queue(target, input);
}
stream_status_t status = rebalance_queues(0, {});
Expand Down
4 changes: 3 additions & 1 deletion clients/drcachesim/scheduler/scheduler_impl.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2023-2025 Google, Inc. All rights reserved.
* Copyright (c) 2023-2026 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -47,6 +47,7 @@
#include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
Expand Down Expand Up @@ -1260,6 +1261,7 @@ class scheduler_dynamic_tmpl_t : public scheduler_impl_tmpl_t<RecordType, Reader
mutex_dbg_owned unsched_lock_;
// Inputs that are unscheduled indefinitely until directly targeted.
input_queue_t unscheduled_priority_;
std::minstd_rand rand_gen_;
};

// Specialized code for replaying schedules: either a recorded dynamic schedule
Expand Down
128 changes: 128 additions & 0 deletions clients/drcachesim/tests/scheduler_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9151,6 +9151,133 @@ test_noise_generator()
assert(found_at_least_one_noise_generator_read);
}

void
test_random_layout()
{
std::cerr << "\n----------------\nTesting random layout\n";
static constexpr int NUM_INPUTS = 8;
static constexpr int NUM_OUTPUTS = 3;
static constexpr int NUM_INSTRS = 9;
static constexpr int QUANTUM_DURATION = 3;
static constexpr int NUM_ITERS = 12;
static constexpr memref_tid_t TID_BASE = 100;
std::vector<trace_entry_t> inputs[NUM_INPUTS];
for (int i = 0; i < NUM_INPUTS; i++) {
memref_tid_t tid = TID_BASE + i;
inputs[i].push_back(test_util::make_thread(tid));
inputs[i].push_back(test_util::make_pid(1));
inputs[i].push_back(test_util::make_version(TRACE_ENTRY_VERSION));
inputs[i].push_back(test_util::make_timestamp(10)); // All the same time priority.
for (int j = 0; j < NUM_INSTRS; j++) {
inputs[i].push_back(test_util::make_instr(42 + j * 4));
}
inputs[i].push_back(test_util::make_exit(tid));
}
std::vector<std::vector<std::string>> sched_string(NUM_ITERS);
for (int iter = 0; iter < NUM_ITERS; ++iter) {
sched_string[iter].reserve(NUM_OUTPUTS);
std::vector<scheduler_t::input_workload_t> sched_inputs;
for (int i = 0; i < NUM_INPUTS; i++) {
std::vector<scheduler_t::input_reader_t> readers;
readers.emplace_back(
std::unique_ptr<test_util::mock_reader_t>(
new test_util::mock_reader_t(inputs[i])),
std::unique_ptr<test_util::mock_reader_t>(new test_util::mock_reader_t()),
TID_BASE + i);
sched_inputs.emplace_back(std::move(readers));
}
scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT,
scheduler_t::DEPENDENCY_IGNORE,
scheduler_t::SCHEDULER_DEFAULTS,
/*verbosity=*/3);
// The first 3rd have no randomness; the next 3rd enable random layout
// with a constant seed; the final 3rd use a different seed each time.
if (iter >= NUM_ITERS * 2 / 3) {
sched_ops.random_initial_layout = iter;
} else if (iter >= NUM_ITERS / 3) {
sched_ops.random_initial_layout = 1;
}
sched_ops.quantum_duration_instrs = QUANTUM_DURATION;
scheduler_t scheduler;
if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) !=
scheduler_t::STATUS_SUCCESS)
assert(false);
sched_string[iter] = run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE);
for (int i = 0; i < NUM_OUTPUTS; i++) {
std::cerr << "iter " << iter << " cpu #" << i
<< " schedule: " << sched_string[iter][i] << "\n";
}
}
// An actual output to illustrate the 1/3 shifts:
// Round robin:
// iter 0 cpu #0 schedule: ..AAA..DDD..GGGAAADDDGGGAAA.DDD.GGG._
// iter 0 cpu #1 schedule: ..BBB..EEE..HHHBBBEEEHHHBBB.EEE.HHH.
// iter 0 cpu #2 schedule: ..CCC..FFFCCCFFFCCC.FFF.____________
// iter 1 cpu #0 schedule: ..AAA..DDD..GGGAAADDDGGGAAA.DDD.GGG._
// iter 1 cpu #1 schedule: ..BBB..EEE..HHHBBBEEEHHHBBB.EEE.HHH.
// iter 1 cpu #2 schedule: ..CCC..FFFCCCFFFCCC.FFF.____________
// iter 2 cpu #0 schedule: ..AAA..DDD..GGGAAADDDGGGAAA.DDD.GGG._
// iter 2 cpu #1 schedule: ..BBB..EEE..HHHBBBEEEHHHBBB.EEE.HHH.
// iter 2 cpu #2 schedule: ..CCC..FFFCCCFFFCCC.FFF.____________
// iter 3 cpu #0 schedule: ..AAA..DDD..GGGAAADDDGGGAAA.DDD.GGG._
// iter 3 cpu #1 schedule: ..BBB..EEE..HHHBBBEEEHHHBBB.EEE.HHH.
// iter 3 cpu #2 schedule: ..CCC..FFFCCCFFFCCC.FFF.____________
// Random with the same seed:
// iter 4 cpu #0 schedule: ..BBB..CCCBBBCCCBBB.CCC._____________
// iter 4 cpu #1 schedule: ..AAA..DDD..EEEAAADDDEEEAAA.DDD.EEE._
// iter 4 cpu #2 schedule: ..FFF..GGG..HHHFFFGGGHHHFFF.GGG.HHH.
// iter 5 cpu #0 schedule: ..BBB..CCCBBBCCCBBB.CCC._____________
// iter 5 cpu #1 schedule: ..AAA..DDD..EEEAAADDDEEEAAA.DDD.EEE._
// iter 5 cpu #2 schedule: ..FFF..GGG..HHHFFFGGGHHHFFF.GGG.HHH.
// iter 6 cpu #0 schedule: ..BBB..CCCBBBCCCBBB.CCC._____________
// iter 6 cpu #1 schedule: ..AAA..DDD..EEEAAADDDEEEAAA.DDD.EEE._
// iter 6 cpu #2 schedule: ..FFF..GGG..HHHFFFGGGHHHFFF.GGG.HHH.
// iter 7 cpu #0 schedule: ..BBB..CCCBBBCCCBBB.CCC._____________
// iter 7 cpu #1 schedule: ..AAA..DDD..EEEAAADDDEEEAAA.DDD.EEE._
// iter 7 cpu #2 schedule: ..FFF..GGG..HHHFFFGGGHHHFFF.GGG.HHH.
// Random with a different seed each time:
// iter 8 cpu #0 schedule: ..BBB..FFF..GGGBBBFFFGGGBBB.FFF.GGG._
// iter 8 cpu #1 schedule: ..DDD..EEE..HHHDDDEEEHHHDDD.EEE.HHH.
// iter 8 cpu #2 schedule: ..AAA..CCCAAACCCAAA.CCC.____________
// iter 9 cpu #0 schedule: ..AAA..BBB..HHHAAABBBHHHAAA.BBB.HHH._
// iter 9 cpu #1 schedule: ..CCC..DDD..EEECCCDDDEEECCC.DDD.EEE.
// iter 9 cpu #2 schedule: ..FFF..GGGFFFGGGFFF.GGG.____________
// iter 10 cpu #0 schedule: ..BBB..CCC..GGGBBBCCCGGGBBB.CCC.GGG._
// iter 10 cpu #1 schedule: ..AAA..EEE..FFFAAAEEEFFFAAA.EEE.FFF.
// iter 10 cpu #2 schedule: ..DDD..HHHDDDHHHDDD.HHH.____________
// iter 11 cpu #0 schedule: ..BBB..CCC..HHHBBBCCCHHHBBB.CCC.HHH._
// iter 11 cpu #1 schedule: ..EEE..GGGEEEGGGEEE.GGG._____________
// iter 11 cpu #2 schedule: ..AAA..DDD..FFFAAADDDFFFAAA.DDD.FFF.
int random_match_count = 0;
for (int iter = 0; iter < NUM_ITERS; ++iter) {
for (int i = 0; i < NUM_OUTPUTS; i++) {
// The first 3rd have no randomness; the next 3rd enable random layout
// with a constant seed; the final 3rd use a different seed each time.
if (iter >= NUM_ITERS * 2 / 3) {
// A different seed should produce a different layout most of
// the time.
if (sched_string[iter][i] == sched_string[iter - 1][i])
++random_match_count;
} else if (iter > NUM_ITERS / 3) {
// The same seed should produce the same layout.
assert(sched_string[iter][i] == sched_string[iter - 1][i]);
} else if (iter == NUM_ITERS / 3) {
// The random shouldn't match the round robin, but there is
// a chance it might so we can't really assert.
// This was tested manually and it did differ.
} else if (iter > 0) {
assert(sched_string[iter][i] == sched_string[iter - 1][i]);
}
}
}
// We want some kind of check; it seems pretty unlikely more than one of the
// random schedules will match another.
// In a test with ctest --repeat_until_failure this passed 1000x in a row
// so this seems a good balance of avoiding flakiness while still checking
// something in an automated fashion.
assert(random_match_count <= NUM_OUTPUTS);
}

int
test_main(int argc, const char *argv[])
{
Expand Down Expand Up @@ -9204,6 +9331,7 @@ test_main(int argc, const char *argv[])
test_marker_updates();
test_options_match();
test_noise_generator();
test_random_layout();

dr_standalone_exit();
return 0;
Expand Down
Loading