Skip to content

Commit

Permalink
i#5843 scheduler: Add option to disable direct switches (#6770)
Browse files Browse the repository at this point in the history
Adds a new scheduler option field honor_direct_switches and a
corresponding command-line parameter -sched_disable_direct_switches to
allow a way to disable direct thread switches, primarily for scheduling
experimentation.

Adds a unit test.

Issue #5843
  • Loading branch information
derekbruening authored Apr 11, 2024
1 parent dd0f289 commit c69c151
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 35 deletions.
1 change: 1 addition & 0 deletions clients/drcachesim/analyzer_multi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ analyzer_multi_tmpl_t<RecordType, ReaderType>::init_dynamic_schedule()
sched_ops.block_time_scale = op_sched_block_scale.get_value();
sched_ops.block_time_max = op_sched_block_max_us.get_value();
sched_ops.randomize_next_input = op_sched_randomize.get_value();
sched_ops.honor_direct_switches = !op_sched_disable_direct_switches.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
9 changes: 9 additions & 0 deletions clients/drcachesim/common/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,15 @@ droption_t<bool> op_sched_randomize(
"set), and FIFO order and instead selects the next input randomly. "
"This is intended for experimental use in sensitivity studies.");

droption_t<bool> op_sched_disable_direct_switches(
DROPTION_SCOPE_FRONTEND, "sched_disable_direct_switches", false,
"Ignore direct thread switch requests",
"Applies to -core_sharded and -core_serial. Disables switching to the recorded "
"targets of TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH system call metadata markers "
"and causes the associated system call to be treated like any other call with a "
"switch being determined by latency and the next input in the queue. The "
"TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH markers are not removed from the trace.");

// Schedule_stats options.
droption_t<uint64_t>
op_schedule_stats_print_every(DROPTION_SCOPE_ALL, "schedule_stats_print_every",
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 @@ -207,6 +207,7 @@ extern dynamorio::droption::droption_t<std::string> op_cpu_schedule_file;
#endif
extern dynamorio::droption::droption_t<std::string> op_sched_switch_file;
extern dynamorio::droption::droption_t<bool> op_sched_randomize;
extern dynamorio::droption::droption_t<bool> op_sched_disable_direct_switches;
extern dynamorio::droption::droption_t<uint64_t> op_schedule_stats_print_every;
extern dynamorio::droption::droption_t<std::string> op_syscall_template_file;
extern dynamorio::droption::droption_t<uint64_t> op_filter_stop_timestamp;
Expand Down
3 changes: 2 additions & 1 deletion clients/drcachesim/scheduler/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2781,7 +2781,8 @@ scheduler_tmpl_t<RecordType, ReaderType>::next_record(output_ordinal_t output,
// boundaries so we live with those being before the switch.
// XXX: Once we insert kernel traces, we may have to try harder
// to stop before the post-syscall records.
if (record_type_is_marker(record, marker_type, marker_value) &&
if (options_.honor_direct_switches &&
record_type_is_marker(record, marker_type, marker_value) &&
marker_type == TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH) {
memref_tid_t target_tid = marker_value;
auto it =
Expand Down
12 changes: 12 additions & 0 deletions clients/drcachesim/scheduler/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,18 @@ template <typename RecordType, typename ReaderType> class scheduler_tmpl_t {
* ahead.
*/
bool read_inputs_in_init = true;
/**
* If true, the scheduler will attempt to switch to the recorded targets of
* #TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH system call metadata markers
* regardless of system call latency. If the target is not available, the
* current implementation will select the next available input in the regular
* scheduling queue, but in the future a forced migration may be applied for an
* input currently on another output. If false, the direct switch markers are
* ignored and only system call latency thresholds are used to determine
* switches (the #TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH markers remain: they
* are not removed from the trace).
*/
bool honor_direct_switches = true;
};

/**
Expand Down
109 changes: 75 additions & 34 deletions clients/drcachesim/tests/scheduler_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3794,42 +3794,83 @@ test_direct_switch()
make_marker(TRACE_MARKER_TYPE_DIRECT_THREAD_SWITCH, TID_BASE + 3),
make_exit(TID_C),
};
std::vector<scheduler_t::input_reader_t> readers;
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_A)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_A);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_B)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_B);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_C)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_C);
// The string constructor writes "." for markers.
// We expect A's first switch to be to C even though B has an earlier timestamp.
// We expect C's direct switch to A to proceed immediately even though A still
// has significant blocked time left. But then after B is scheduled and finishes,
// we still have to wait for C's block time so we see idle underscores:
static const char *const CORE0_SCHED_STRING =
"...AA.........CC......A....BBBB.______________C...";
{
// Test the defaults with direct switches enabled.
std::vector<scheduler_t::input_reader_t> readers;
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_A)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_A);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_B)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_B);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_C)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_C);
// The string constructor writes "." for markers.
// We expect A's first switch to be to C even though B has an earlier timestamp.
// We expect C's direct switch to A to proceed immediately even though A still
// has significant blocked time left. But then after B is scheduled and finishes,
// we still have to wait for C's block time so we see idle underscores:
static const char *const CORE0_SCHED_STRING =
"...AA.........CC......A....BBBB.______________C...";

std::vector<scheduler_t::input_workload_t> sched_inputs;
sched_inputs.emplace_back(std::move(readers));
scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT,
scheduler_t::DEPENDENCY_TIMESTAMPS,
scheduler_t::SCHEDULER_DEFAULTS,
/*verbosity=*/3);
sched_ops.quantum_duration = QUANTUM_DURATION;
// We use our mock's time==instruction count for a deterministic result.
sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME;
sched_ops.blocking_switch_threshold = BLOCK_LATENCY;
sched_ops.block_time_scale = BLOCK_SCALE;
scheduler_t scheduler;
if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) !=
scheduler_t::STATUS_SUCCESS)
assert(false);
std::vector<std::string> sched_as_string =
run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true);
for (int i = 0; i < NUM_OUTPUTS; i++) {
std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n";
std::vector<scheduler_t::input_workload_t> sched_inputs;
sched_inputs.emplace_back(std::move(readers));
scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT,
scheduler_t::DEPENDENCY_TIMESTAMPS,
scheduler_t::SCHEDULER_DEFAULTS,
/*verbosity=*/3);
sched_ops.quantum_duration = QUANTUM_DURATION;
// We use our mock's time==instruction count for a deterministic result.
sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME;
sched_ops.blocking_switch_threshold = BLOCK_LATENCY;
sched_ops.block_time_scale = BLOCK_SCALE;
scheduler_t scheduler;
if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) !=
scheduler_t::STATUS_SUCCESS)
assert(false);
std::vector<std::string> sched_as_string =
run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true);
for (int i = 0; i < NUM_OUTPUTS; i++) {
std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n";
}
assert(sched_as_string[0] == CORE0_SCHED_STRING);
}
{
// Test disabling direct switches.
std::vector<scheduler_t::input_reader_t> readers;
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_A)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_A);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_B)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_B);
readers.emplace_back(std::unique_ptr<mock_reader_t>(new mock_reader_t(refs_C)),
std::unique_ptr<mock_reader_t>(new mock_reader_t()), TID_C);
// The string constructor writes "." for markers.
// We expect A's first switch to be to B with an earlier timestamp.
// We expect C's direct switch to A to not happen until A's blocked time ends.
static const char *const CORE0_SCHED_STRING =
"...AA.........BBBB....CC......___________________C...___A.";

std::vector<scheduler_t::input_workload_t> sched_inputs;
sched_inputs.emplace_back(std::move(readers));
scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT,
scheduler_t::DEPENDENCY_TIMESTAMPS,
scheduler_t::SCHEDULER_DEFAULTS,
/*verbosity=*/3);
sched_ops.quantum_duration = QUANTUM_DURATION;
// We use our mock's time==instruction count for a deterministic result.
sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME;
sched_ops.blocking_switch_threshold = BLOCK_LATENCY;
sched_ops.block_time_scale = BLOCK_SCALE;
sched_ops.honor_direct_switches = false;
scheduler_t scheduler;
if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) !=
scheduler_t::STATUS_SUCCESS)
assert(false);
std::vector<std::string> sched_as_string =
run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_BASE, /*send_time=*/true);
for (int i = 0; i < NUM_OUTPUTS; i++) {
std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n";
}
assert(sched_as_string[0] == CORE0_SCHED_STRING);
}
assert(sched_as_string[0] == CORE0_SCHED_STRING);
}

static void
Expand Down

0 comments on commit c69c151

Please sign in to comment.