-
-
Save divi255/dead86a7923465285d4941367733d9a4 to your computer and use it in GitHub Desktop.
/* build with | |
gcc -c -Wall -Werror -fpic -O3 driver.c | |
gcc -shared -o libmydriver.so -O3 driver.o | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
struct Data { | |
char port[20]; | |
int status; | |
char value[256]; | |
}; | |
struct Data *Result; | |
int current_result = 0; | |
int get_bulk() { | |
int n = 100; | |
Result = malloc(n * sizeof(struct Data)); | |
for (int i=0; i<n; i++) { | |
int i_label = i + 1; | |
sprintf(Result[i].port, "PORT %u", i_label); | |
Result[i].status = i; | |
sprintf(Result[i].value, "VALUE FOR %u", i_label); | |
} | |
return n; | |
} | |
struct Data *get_next_result() { | |
return &Result[current_result++]; | |
} | |
void free_result() { | |
free(Result); | |
current_result = 0; | |
} |
/* build with | |
cargo build --release | |
*/ | |
type PortName = [i8; 20]; | |
type StateStatus = i32; | |
type StateValue = [i8; 256]; | |
#[derive(Debug)] | |
#[repr(C)] | |
pub struct Data { | |
port: PortName, | |
status: StateStatus, | |
value: StateValue, | |
} | |
impl Data { | |
fn new() -> Self { | |
Self { | |
port: [0; 20], | |
status: -0xff, | |
value: [0; 256], | |
} | |
} | |
} | |
struct DataResult { | |
data: Vec<Data>, | |
current: usize, | |
} | |
impl DataResult { | |
unsafe fn get_next(&mut self) -> &Data { | |
match self.data.get(self.current) { | |
Some(v) => { | |
self.current += 1; | |
v | |
} | |
None => panic!("Result overflow"), | |
} | |
} | |
fn clear(&mut self) { | |
self.data.clear(); | |
self.current = 0; | |
} | |
} | |
fn fill_c_string(s: &str, buf: &mut [i8]) { | |
let b = s.as_bytes(); | |
if b.len() > buf.len() - 1 { | |
panic!("string overflow"); | |
} else { | |
for i in 0..b.len() { | |
buf[i] = b[i] as i8; | |
} | |
buf[b.len()] = 0; | |
} | |
} | |
static mut D: DataResult = DataResult { | |
data: Vec::new(), | |
current: 0, | |
}; | |
#[no_mangle] | |
pub unsafe fn get_bulk() -> i32 { | |
let n = 100; | |
D.clear(); | |
for i in 0..n { | |
let i_label = i + 1; | |
let mut d = Data::new(); | |
d.status = i; | |
fill_c_string(&format!("PORT {}", i_label), &mut d.port); | |
fill_c_string(&format!("VALUE FOR {}", i_label), &mut d.value); | |
D.data.push(d); | |
} | |
n | |
} | |
#[no_mangle] | |
pub unsafe fn get_next_result<'a>() -> &'a Data { | |
D.get_next() | |
} | |
#[no_mangle] | |
pub unsafe fn free_result() { | |
D.clear(); | |
} |
#[derive(Debug)]
#[repr(C)]
struct Data {
port: [i8; 20],
status: i32,
value: [i8; 256],
}
type GetBulk = fn() -> i32;
type GetNextResult = fn() -> &'static Data;
fn benchmark(library_path: &str, iterations: u64) {
let lib = unsafe { Library::new(library_path) }.unwrap();
let get_bulk: Symbol<GetBulk> = unsafe { lib.get(b"get_bulk") }.unwrap();
let get_next_result: Symbol<GetNextResult> = unsafe { lib.get(b"get_next_result") }.unwrap();
let free_result: Symbol<fn()> = unsafe { lib.get(b"free_result") }.unwrap();
let start = Instant::now();
for _ in 0..iterations {
let count = get_bulk();
assert_eq!(count, 100);
for x in 0..count {
let data = get_next_result();
let _port = unsafe { CStr::from_ptr(data.port.as_ptr()) }
.to_str()
.unwrap();
let _value = unsafe { CStr::from_ptr(data.value.as_ptr()) }
.to_str()
.unwrap();
assert_eq!(data.status, x);
}
free_result();
}
let spent = start.elapsed();
println!(
"{}: {} ({} iters/s)",
library_path.rsplit("/").next().unwrap(),
spent.as_secs_f64(),
(iterations as f64 / spent.as_secs_f64()) as u64
);
}
You are comparing Rust format!
vs C printf
, not C vs Rust (and not gcc vs rustc). That can be easily seen in the profiler. Rust inlines all the formatting logic so the compiler can optimize it while printf just calls generic implementation from libc. That's why rust .so weights about 3MiB comparing to 15KiB of C .so. Your benchmark of fast and more realistic C runs about 3.5 times faster than your implementation in Rust; version in modern C++ does it even better. Note that with fast C / C++ code in your benchmark is much slower than the library; for example, version of get_bulk
in C++ takes about 15% of CPU time of the benchmark.
$ ./benchmark/target/release/benchmark ./libmydriver-c-fast.so ./libmydriver-cpp.so ./libmydriver-c.so ./libmydriver-rs.so
libmydriver-c-fast.so: 0.333182013 (300136 iters/s)
libmydriver-cpp.so: 0.295337787 (338595 iters/s)
libmydriver-c.so: 1.360962362 (73477 iters/s)
libmydriver-rs.so: 1.220750513 (81916 iters/s)
$ make hyperfine
...
hyperfine \
'benchmark/target/release/benchmark ./libmydriver-c.so' \
'benchmark/target/release/benchmark ./libmydriver-c-fast.so' \
'benchmark/target/release/benchmark ./libmydriver-cpp.so' \
'benchmark/target/release/benchmark ./libmydriver-rs.so'
Benchmark #1: benchmark/target/release/benchmark ./libmydriver-c.so
Time (mean ± σ): 1.395 s ± 0.025 s [User: 1.393 s, System: 0.001 s]
Range (min … max): 1.366 s … 1.444 s 10 runs
Benchmark #2: benchmark/target/release/benchmark ./libmydriver-c-fast.so
Time (mean ± σ): 347.4 ms ± 21.5 ms [User: 346.9 ms, System: 1.2 ms]
Range (min … max): 320.9 ms … 376.5 ms 10 runs
Benchmark #3: benchmark/target/release/benchmark ./libmydriver-cpp.so
Time (mean ± σ): 306.5 ms ± 14.9 ms [User: 304.0 ms, System: 2.4 ms]
Range (min … max): 295.2 ms … 333.7 ms 10 runs
Benchmark #4: benchmark/target/release/benchmark ./libmydriver-rs.so
Time (mean ± σ): 1.269 s ± 0.025 s [User: 1.265 s, System: 0.003 s]
Range (min … max): 1.245 s … 1.319 s 10 runs
Summary
'benchmark/target/release/benchmark ./libmydriver-cpp.so' ran
1.13 ± 0.09 times faster than 'benchmark/target/release/benchmark ./libmydriver-c-fast.so'
4.14 ± 0.22 times faster than 'benchmark/target/release/benchmark ./libmydriver-rs.so'
4.55 ± 0.23 times faster than 'benchmark/target/release/benchmark ./libmydriver-c.so'
The code is here.
You are comparing Rust
format!
vs Cprintf
, not C vs Rust (and not gcc vs rustc). That can be easily seen in the
bingo! thanks man
Hi,
Can you share what your benchmark does? Otherwise it's not clear what is the purpose of this code.