Skip to content

Commit 11c34cd

Browse files
evan-schottduc-nxbhoberman
authored andcommitted
Benchmarking suite (nexus-xyz#371)
* WIP * add benchmarking * fmt * fix * x * Update tests/testing-framework/src/emulator.rs Co-authored-by: duc-nx <duc@nexus.xyz> * comments + wrapper function for serialization/assertions * fix * Update tests/testing-framework/Cargo.toml Co-authored-by: Ben Hoberman <bhoberman@users.noreply.github.com> * Update .gitignore Co-authored-by: Ben Hoberman <bhoberman@users.noreply.github.com> * rebase fix * fix test * fmt * fmt * fix * fix * x * harvard * revise --------- Co-authored-by: duc-nx <duc@nexus.xyz> Co-authored-by: Ben Hoberman <bhoberman@users.noreply.github.com>
1 parent d5e5909 commit 11c34cd

File tree

5 files changed

+623
-307
lines changed

5 files changed

+623
-307
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ nexus-proof
1717

1818
# macos
1919
.DS_Store
20+
21+
# benchmarks
22+
benchmark_results.csv

tests/testing-framework/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ categories.workspace = true
1010
publish.workspace = true
1111

1212
[dependencies]
13-
tempfile = "3.13.0"
1413
nexus-common = { path = "../../common" }
1514
nexus-vm = { path = "../../vm" }
15+
nexus-rt = { path = "../../runtime" }
1616
postcard = { version = "1.0.10", features = ["alloc"] }
1717
serde.workspace = true
18+
tempfile = "3.13"
1819

1920
[dev-dependencies]
2021
serial_test = "3.2.0"
22+
chrono = "0.4"
23+
libc = "0.2"
24+
25+
[[bench]]
26+
name = "benchmarks"
27+
harness = true
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#![feature(test)]
2+
3+
extern crate test;
4+
5+
use libc::{getrusage, rusage, RUSAGE_CHILDREN, RUSAGE_SELF};
6+
use nexus_vm::elf::ElfFile;
7+
use std::{
8+
fs::OpenOptions,
9+
io::Write,
10+
path::PathBuf,
11+
process::Command,
12+
time::{Duration, Instant},
13+
};
14+
use test::Bencher;
15+
use testing_framework::emulator::{
16+
compile_to_elf, create_tmp_dir, emulate, setup_project, EmulatorType,
17+
};
18+
19+
/// Performance metrics collected during benchmarking.
20+
#[derive(Debug)]
21+
struct BenchmarkMetrics {
22+
/// Guest CPU cycles per second in MHz.
23+
cycle_mega_hz: f32,
24+
/// Ratio of emulation time to native execution time.
25+
overhead: f32,
26+
/// Time taken by native execution.
27+
native_duration: Duration,
28+
/// Time taken by emulated execution.
29+
emulation_duration: Duration,
30+
/// Native execution system CPU time.
31+
native_sys_time: Duration,
32+
/// Native execution user CPU time.
33+
native_user_time: Duration,
34+
/// Emulation system CPU time.
35+
emulation_sys_time: Duration,
36+
/// Emulation user CPU time.
37+
emulation_user_time: Duration,
38+
}
39+
40+
/// Executes and measures the native execution speed of a Rust program.
41+
fn measure_native_execution(path: &PathBuf) -> (Duration, Duration, Duration) {
42+
// Build with release optimizations.
43+
let output = Command::new("cargo")
44+
.current_dir(path)
45+
.arg("build")
46+
.arg("--release")
47+
.output()
48+
.expect("Failed to build project");
49+
50+
assert!(output.status.success(), "Native build failed");
51+
52+
let start_usage = start_timer(RUSAGE_CHILDREN);
53+
let start = Instant::now();
54+
55+
let output = Command::new("cargo")
56+
.current_dir(path)
57+
.arg("run")
58+
.arg("--release")
59+
.output()
60+
.expect("Failed to run project");
61+
62+
assert!(output.status.success(), "Native execution failed");
63+
64+
let total_time = start.elapsed();
65+
let (user_time, sys_time) = stop_timer(&start_usage, RUSAGE_CHILDREN);
66+
67+
(total_time, user_time, sys_time)
68+
}
69+
70+
/// Records benchmark results to a CSV file for analysis.
71+
fn record_benchmark_results(metrics: &BenchmarkMetrics, test: &str, emulator_type: EmulatorType) {
72+
let mut file = OpenOptions::new()
73+
.create(true)
74+
.append(true)
75+
.open("benchmark_results.csv")
76+
.expect("Failed to open benchmark_results.csv");
77+
78+
// Write header if file is empty.
79+
if file.metadata().unwrap().len() == 0 {
80+
writeln!(
81+
file,
82+
"cycle_mega_hz,overhead,native_duration,emulation_duration,native_sys_time,native_user_time,emulation_sys_time,emulation_user_time,test,emulator_type,timestamp"
83+
)
84+
.expect("Failed to write CSV header");
85+
}
86+
87+
// Record results with emulator-specific naming.
88+
let emulator_name = match emulator_type {
89+
EmulatorType::Harvard => "Harvard",
90+
EmulatorType::Linear(_, _, _) => "Linear",
91+
EmulatorType::TwoPass => "TwoPass",
92+
};
93+
94+
writeln!(
95+
file,
96+
"{},{},{},{},{},{},{},{},{},{},{}",
97+
metrics.cycle_mega_hz,
98+
metrics.overhead,
99+
metrics.native_duration.as_secs_f32(),
100+
metrics.emulation_duration.as_secs_f32(),
101+
metrics.native_sys_time.as_secs_f32(),
102+
metrics.native_user_time.as_secs_f32(),
103+
metrics.emulation_sys_time.as_secs_f32(),
104+
metrics.emulation_user_time.as_secs_f32(),
105+
test,
106+
emulator_name,
107+
chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
108+
)
109+
.expect("Failed to write benchmark results");
110+
}
111+
112+
/// Benchmarks a test program using specified emulator configuration.
113+
fn run_benchmark(
114+
test: &str,
115+
compile_flags: &str,
116+
emulator_type: EmulatorType,
117+
public_input: Vec<u8>,
118+
private_input: Vec<u8>,
119+
output: Vec<u8>,
120+
) {
121+
// Set up temporary project directory.
122+
let tmp_dir = create_tmp_dir();
123+
let tmp_project_path = tmp_dir.path().join("integration");
124+
125+
// Compile test to RISC-V ELF.
126+
let test_dir_path = "../integration-tests";
127+
let test_path = format!("{test_dir_path}/{test}.rs");
128+
setup_project(&tmp_project_path, &test_path);
129+
let elf_contents = compile_to_elf(&tmp_project_path, compile_flags);
130+
131+
// Measure native execution time.
132+
let (native_duration, native_user_time, native_sys_time) =
133+
measure_native_execution(&tmp_project_path);
134+
135+
// Parse and prepare ELF for emulation.
136+
let elf = ElfFile::from_bytes(&elf_contents).expect("Failed to parse ELF file");
137+
138+
// Measure emulation time.
139+
let start_usage = start_timer(RUSAGE_SELF);
140+
let start = Instant::now();
141+
let (cycles, _) = emulate(
142+
vec![elf],
143+
public_input,
144+
private_input,
145+
output.len(),
146+
emulator_type.clone(),
147+
);
148+
let emulation_duration = start.elapsed();
149+
let (emulation_user_time, emulation_sys_time) = stop_timer(&start_usage, RUSAGE_SELF);
150+
151+
// Calculate performance metrics.
152+
let metrics = BenchmarkMetrics {
153+
cycle_mega_hz: (cycles[0] as f32 / emulation_duration.as_secs_f32()) / 1_000_000.0,
154+
overhead: emulation_duration.as_secs_f32() / native_duration.as_secs_f32(),
155+
native_duration,
156+
emulation_duration,
157+
native_sys_time,
158+
native_user_time,
159+
emulation_sys_time,
160+
emulation_user_time,
161+
};
162+
163+
record_benchmark_results(&metrics, test, emulator_type);
164+
}
165+
166+
#[test]
167+
fn test_benchmark_harvard() {
168+
// Delete any existing benchmark results file.
169+
if let Ok(_) = OpenOptions::new().write(true).open("benchmark_results.csv") {
170+
std::fs::remove_file("benchmark_results.csv")
171+
.expect("Failed to delete benchmark results file");
172+
}
173+
run_benchmark(
174+
"../../examples/src/fib1000",
175+
"-C opt-level=3",
176+
EmulatorType::Harvard,
177+
Vec::new(),
178+
Vec::new(),
179+
Vec::new(),
180+
);
181+
}
182+
183+
/// Benchmark Harvard emulator performance.
184+
#[bench]
185+
fn bench_harvard_fib1000(b: &mut Bencher) {
186+
b.iter(|| {
187+
run_benchmark(
188+
"../../examples/src/fib1000",
189+
"-C opt-level=3",
190+
EmulatorType::Harvard,
191+
Vec::new(),
192+
Vec::new(),
193+
Vec::new(),
194+
);
195+
});
196+
}
197+
198+
/// Benchmark Linear emulator performance.
199+
#[bench]
200+
fn bench_linear_fib1000(b: &mut Bencher) {
201+
b.iter(|| {
202+
run_benchmark(
203+
"../../examples/src/fib1000",
204+
"-C opt-level=3",
205+
EmulatorType::default_linear(),
206+
Vec::new(),
207+
Vec::new(),
208+
Vec::new(),
209+
);
210+
});
211+
}
212+
213+
/// Benchmark Two-Pass emulator performance.
214+
#[bench]
215+
fn bench_twopass_fib1000(b: &mut Bencher) {
216+
b.iter(|| {
217+
run_benchmark(
218+
"../../examples/src/fib1000",
219+
"-C opt-level=3",
220+
EmulatorType::TwoPass,
221+
Vec::new(),
222+
Vec::new(),
223+
Vec::new(),
224+
);
225+
});
226+
}
227+
228+
/// Start timing and return resource usage
229+
fn start_timer(usage_type: i32) -> rusage {
230+
let mut usage: rusage = unsafe { std::mem::zeroed() };
231+
unsafe { getrusage(usage_type, &mut usage) };
232+
usage
233+
}
234+
235+
/// Stop timing and return user and system time differences
236+
fn stop_timer(start_usage: &rusage, usage_type: i32) -> (Duration, Duration) {
237+
let mut end_usage: rusage = unsafe { std::mem::zeroed() };
238+
unsafe { getrusage(usage_type, &mut end_usage) };
239+
calculate_time_diff(start_usage, &end_usage)
240+
}
241+
242+
/// Calculate user and system time differences between two rusage measurements
243+
fn calculate_time_diff(start_usage: &rusage, end_usage: &rusage) -> (Duration, Duration) {
244+
let user_sec_diff = end_usage.ru_utime.tv_sec - start_usage.ru_utime.tv_sec;
245+
let user_usec_diff = end_usage.ru_utime.tv_usec - start_usage.ru_utime.tv_usec;
246+
247+
let sys_sec_diff = end_usage.ru_stime.tv_sec - start_usage.ru_stime.tv_sec;
248+
let sys_usec_diff = end_usage.ru_stime.tv_usec - start_usage.ru_stime.tv_usec;
249+
250+
// Handle negative microsecond differences by borrowing from seconds
251+
let (user_sec, user_usec) = if user_usec_diff < 0 {
252+
(user_sec_diff - 1, user_usec_diff + 1_000_000)
253+
} else {
254+
(user_sec_diff, user_usec_diff)
255+
};
256+
257+
let (sys_sec, sys_usec) = if sys_usec_diff < 0 {
258+
(sys_sec_diff - 1, sys_usec_diff + 1_000_000)
259+
} else {
260+
(sys_sec_diff, sys_usec_diff)
261+
};
262+
263+
let user_time = Duration::from_secs(user_sec as u64) + Duration::from_micros(user_usec as u64);
264+
let sys_time = Duration::from_secs(sys_sec as u64) + Duration::from_micros(sys_usec as u64);
265+
266+
(user_time, sys_time)
267+
}

0 commit comments

Comments
 (0)