Skip to content

Commit

Permalink
Add a simple VM opcode throughput test.
Browse files Browse the repository at this point in the history
  • Loading branch information
rdaum committed Jan 7, 2024
1 parent a36adf9 commit 1c51aa7
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 15 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/db/benches/tb_single_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub fn workload_commits(c: &mut Criterion) {
let rt = Runtime::new().unwrap();

let mut group = c.benchmark_group("throughput");
group.sample_size(100);
group.sample_size(1000);
group.measurement_time(Duration::from_secs(10));
group.throughput(criterion::Throughput::Elements(tx_count as u64));
group.bench_function("commit_rate", |b| {
Expand Down
5 changes: 5 additions & 0 deletions crates/kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ inventory.workspace = true
test-case.workspace = true
unindent.workspace = true
pretty_assertions.workspace = true
criterion.workspace = true

[[test]]
name = "basic-testsuite"
path = "testsuite/basic/basic_suite.rs"

[[bench]]
name = "vm_benches"
harness = false

[dependencies]

## Own
Expand Down
187 changes: 187 additions & 0 deletions crates/kernel/benches/vm_benches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! Benchmarks of various virtual machine executions
//! In general attempting to keep isolated from the object/world-state and simply execute
//! program code that doesn't interact with the DB, to measure opcode execution efficiency.
use std::sync::Arc;
use std::time::Duration;

use criterion::{criterion_group, criterion_main, Criterion};
use tokio::runtime::Runtime;

use moor_compiler::codegen::compile;
use moor_db::tb_worldstate::TupleBoxWorldStateSource;
use moor_kernel::tasks::scheduler::AbortLimitReason;
use moor_kernel::tasks::sessions::{NoopClientSession, Session};
use moor_kernel::tasks::vm_host::{VMHostResponse, VmHost};
use moor_kernel::tasks::VerbCall;
use moor_values::model::r#match::VerbArgsSpec;
use moor_values::model::verbs::{BinaryType, VerbFlag};
use moor_values::model::world_state::{WorldState, WorldStateSource};
use moor_values::model::CommitResult;
use moor_values::util::bitenum::BitEnum;
use moor_values::var::Var;
use moor_values::{AsByteBuffer, NOTHING, SYSTEM_OBJECT};

async fn create_worldstate() -> TupleBoxWorldStateSource {
TupleBoxWorldStateSource::open(None, 1 << 30).await.0;
let (ws_source, _) = TupleBoxWorldStateSource::open(None, 1 << 30).await;
let mut tx = ws_source.new_world_state().await.unwrap();
let _sysobj = tx
.create_object(SYSTEM_OBJECT, NOTHING, SYSTEM_OBJECT, BitEnum::all())
.await
.unwrap();
assert_eq!(tx.commit().await.unwrap(), CommitResult::Success);
ws_source
}

pub async fn prepare_call_verb(
world_state: &mut dyn WorldState,
session: Arc<dyn Session>,
verb_name: &str,
args: Vec<Var>,
max_ticks: usize,
) -> VmHost {
let (scs_tx, _scs_rx) = tokio::sync::mpsc::unbounded_channel();
let mut vm_host = VmHost::new(
20,
max_ticks,
Duration::from_secs(15),
session.clone(),
scs_tx,
);

let vi = world_state
.find_method_verb_on(SYSTEM_OBJECT, SYSTEM_OBJECT, verb_name)
.await
.unwrap();
vm_host
.start_call_method_verb(
0,
SYSTEM_OBJECT,
vi,
VerbCall {
verb_name: verb_name.to_string(),
location: SYSTEM_OBJECT,
this: SYSTEM_OBJECT,
player: SYSTEM_OBJECT,
args,
argstr: "".to_string(),
caller: SYSTEM_OBJECT,
},
)
.await;
vm_host
}

async fn prepare_vm_execution(
ws_source: &mut TupleBoxWorldStateSource,
program: &str,
max_ticks: usize,
) -> VmHost {
let binary = compile(program).unwrap();
let mut tx = ws_source.new_world_state().await.unwrap();
tx.add_verb(
SYSTEM_OBJECT,
SYSTEM_OBJECT,
vec!["test".to_string()],
SYSTEM_OBJECT,
VerbFlag::rxd(),
VerbArgsSpec::this_none_this(),
binary.make_copy_as_vec(),
BinaryType::LambdaMoo18X,
)
.await
.unwrap();
let session = Arc::new(NoopClientSession::new());
let vm_host = prepare_call_verb(tx.as_mut(), session, "test", vec![], max_ticks).await;
assert_eq!(tx.commit().await.unwrap(), CommitResult::Success);
vm_host
}

/// Run the vm host until it runs out of ticks
async fn execute(world_state: &mut dyn WorldState, vm_host: &mut VmHost) -> bool {
vm_host.reset_ticks();
vm_host.reset_time();

// Call repeatedly into exec until we ge either an error or Complete.
loop {
match vm_host.exec_interpreter(0, world_state).await {
VMHostResponse::ContinueOk => {
continue;
}
VMHostResponse::AbortLimit(AbortLimitReason::Ticks(_)) => {
return true;
}
VMHostResponse::CompleteSuccess(_) => {
return false;
}
VMHostResponse::AbortLimit(AbortLimitReason::Time(time)) => {
panic!("Unexpected abort: {:?}", time);
}
VMHostResponse::DispatchFork(f) => {
panic!("Unexpected fork: {:?}", f);
}
VMHostResponse::CompleteException(e) => {
panic!("Unexpected exception: {:?}", e)
}
VMHostResponse::Suspend(_) => {
panic!("Unexpected suspend");
}
VMHostResponse::SuspendNeedInput => {
panic!("Unexpected suspend need input");
}
VMHostResponse::CompleteAbort => {
panic!("Unexpected abort");
}
}
}
}

async fn do_program(program: &str, max_ticks: usize, iters: u64) -> Duration {
let mut cumulative = Duration::new(0, 0);

let mut state_source = create_worldstate().await;
let mut vm_host = prepare_vm_execution(&mut state_source, program, max_ticks).await;
let mut tx = state_source.new_world_state().await.unwrap();
for _ in 0..iters {
let start = std::time::Instant::now();
let _ = execute(tx.as_mut(), &mut vm_host).await;
let end = std::time::Instant::now();
cumulative += end - start;
}
tx.rollback().await.unwrap();

cumulative
}

fn opcode_throughput(c: &mut Criterion) {
let rt = Runtime::new().unwrap();

let mut group = c.benchmark_group("opcode_throughput");
group.sample_size(1000);
group.measurement_time(Duration::from_secs(10));

let num_ticks = 30000;
group.throughput(criterion::Throughput::Elements(num_ticks as u64));
group.bench_function("while_loop", |b| {
b.to_async(&rt)
.iter_custom(|iters| do_program("while (1) endwhile", num_ticks, iters));
});
group.bench_function("while_increment_var_loop", |b| {
b.to_async(&rt)
.iter_custom(|iters| do_program("i = 0; while(1) i=i+1; endwhile", num_ticks, iters));
});
group.bench_function("for_in_range_loop", |b| {
b.to_async(&rt).iter_custom(|iters| {
do_program(
"while(1) for i in [1..1000000] endfor endwhile",
num_ticks,
iters,
)
});
});
group.finish();
}

criterion_group!(benches, opcode_throughput);
criterion_main!(benches);
6 changes: 6 additions & 0 deletions crates/kernel/src/tasks/vm_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ impl VmHost {
.unwrap_or(0)
}

pub fn reset_ticks(&mut self) {
self.vm_exec_state.tick_count = 0;
}
pub fn reset_time(&mut self) {
self.vm_exec_state.start_time = Some(SystemTime::now());
}
pub fn args(&self) -> Vec<Var> {
self.vm_exec_state.top().args.clone()
}
Expand Down
27 changes: 13 additions & 14 deletions crates/kernel/src/vm/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ pub(crate) struct Activation {
pub(crate) bf_trampoline_arg: Option<Var>,
}

fn set_constants(a: &mut Activation) {
a.set_gvar(GlobalName::NUM, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::OBJ, v_int(VarType::TYPE_OBJ as i64));
a.set_gvar(GlobalName::STR, v_int(VarType::TYPE_STR as i64));
a.set_gvar(GlobalName::ERR, v_int(VarType::TYPE_ERR as i64));
a.set_gvar(GlobalName::LIST, v_int(VarType::TYPE_LIST as i64));
a.set_gvar(GlobalName::INT, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::FLOAT, v_int(VarType::TYPE_FLOAT as i64));
}

impl Activation {
pub fn for_call(task_id: TaskId, verb_call_request: VerbExecutionRequest) -> Self {
let program = verb_call_request.program;
Expand All @@ -139,16 +149,11 @@ impl Activation {
permissions: verb_owner,
};

set_constants(&mut a);

a.set_gvar(GlobalName::this, v_objid(verb_call_request.call.this));
a.set_gvar(GlobalName::player, v_objid(verb_call_request.call.player));
a.set_gvar(GlobalName::caller, v_objid(verb_call_request.call.caller));
a.set_gvar(GlobalName::NUM, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::OBJ, v_int(VarType::TYPE_OBJ as i64));
a.set_gvar(GlobalName::STR, v_int(VarType::TYPE_STR as i64));
a.set_gvar(GlobalName::ERR, v_int(VarType::TYPE_ERR as i64));
a.set_gvar(GlobalName::LIST, v_int(VarType::TYPE_LIST as i64));
a.set_gvar(GlobalName::INT, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::FLOAT, v_int(VarType::TYPE_FLOAT as i64));
a.set_gvar(
GlobalName::verb,
v_str(verb_call_request.call.verb_name.as_str()),
Expand Down Expand Up @@ -215,16 +220,10 @@ impl Activation {
permissions,
};

set_constants(&mut a);
a.set_gvar(GlobalName::this, v_objid(player));
a.set_gvar(GlobalName::player, v_objid(player));
a.set_gvar(GlobalName::caller, v_objid(player));
a.set_gvar(GlobalName::NUM, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::OBJ, v_int(VarType::TYPE_OBJ as i64));
a.set_gvar(GlobalName::STR, v_int(VarType::TYPE_STR as i64));
a.set_gvar(GlobalName::ERR, v_int(VarType::TYPE_ERR as i64));
a.set_gvar(GlobalName::LIST, v_int(VarType::TYPE_LIST as i64));
a.set_gvar(GlobalName::INT, v_int(VarType::TYPE_INT as i64));
a.set_gvar(GlobalName::FLOAT, v_int(VarType::TYPE_FLOAT as i64));
a.set_gvar(GlobalName::verb, v_str("eval"));
a.set_gvar(GlobalName::args, v_empty_list());
a.set_gvar(GlobalName::argstr, v_str(""));
Expand Down

0 comments on commit 1c51aa7

Please sign in to comment.