Skip to content

Instantly share code, notes, and snippets.

@divi255
Last active September 6, 2021 22:52
Show Gist options
  • Save divi255/dead86a7923465285d4941367733d9a4 to your computer and use it in GitHub Desktop.
Save divi255/dead86a7923465285d4941367733d9a4 to your computer and use it in GitHub Desktop.
C driver vs Rust driver
/* 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();
}
@divi255
Copy link
Author

divi255 commented Aug 24, 2021

#[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
    );
}

@BigRedEye
Copy link

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.

@divi255
Copy link
Author

divi255 commented Sep 6, 2021

You are comparing Rust format! vs C printf, not C vs Rust (and not gcc vs rustc). That can be easily seen in the

bingo! thanks man

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment