Skip to content

Instantly share code, notes, and snippets.

@BigRedEye
Last active September 7, 2021 04:35
Show Gist options
  • Save BigRedEye/4cef5aeba06817780d403ef397829bdd to your computer and use it in GitHub Desktop.
Save BigRedEye/4cef5aeba06817780d403ef397829bdd to your computer and use it in GitHub Desktop.
Stupid driver

Rust is faster than {C,C++,You name it}

The benchmark itself is slow, actual difference in performance is much higher.

$ make benchmark
...
/usr/bin/clang++ --version
clang version 12.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

/usr/bin/clang --version
clang version 12.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

rustc --version
rustc 1.54.0 (a178d0322 2021-07-26)
...
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.417 s ±  0.015 s    [User: 1.415 s, System: 0.002 s]
  Range (min … max):    1.395 s …  1.452 s    10 runs
 
Benchmark #2: benchmark/target/release/benchmark ./libmydriver-c-fast.so
  Time (mean ± σ):     343.1 ms ±  19.8 ms    [User: 340.3 ms, System: 2.9 ms]
  Range (min … max):   321.7 ms … 376.5 ms    10 runs
 
Benchmark #3: benchmark/target/release/benchmark ./libmydriver-cpp.so
  Time (mean ± σ):     308.0 ms ±  15.3 ms    [User: 307.5 ms, System: 0.9 ms]
  Range (min … max):   290.5 ms … 338.2 ms    10 runs
 
Benchmark #4: benchmark/target/release/benchmark ./libmydriver-rs.so
  Time (mean ± σ):      1.249 s ±  0.026 s    [User: 1.249 s, System: 0.001 s]
  Range (min … max):    1.206 s …  1.276 s    10 runs
 
Summary
  'benchmark/target/release/benchmark ./libmydriver-cpp.so' ran
    1.11 ± 0.08 times faster than 'benchmark/target/release/benchmark ./libmydriver-c-fast.so'
    4.06 ± 0.22 times faster than 'benchmark/target/release/benchmark ./libmydriver-rs.so'
    4.60 ± 0.23 times faster than 'benchmark/target/release/benchmark ./libmydriver-c.so'

Building

Run make fold_dirs before build (gist does not support directories)

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "benchmark"
version = "0.1.0"
dependencies = [
"libloading",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "libloading"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[package]
name = "benchmark"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libloading = "0.7.0"
use libloading::{Library, Symbol};
#[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 = std::time::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 { std::ffi::CStr::from_ptr(data.port.as_ptr()) }
.to_str()
.unwrap();
let value = unsafe { std::ffi::CStr::from_ptr(data.value.as_ptr()) }
.to_str()
.unwrap();
assert_eq!(data.status, x);
debug_assert_eq!(port, format!("PORT {}", x + 1));
debug_assert_eq!(value, format!("VALUE FOR {}", x + 1));
}
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
);
}
fn main() {
for path in std::env::args().skip(1) {
benchmark(&path, 100000);
}
}
#include <stddef.h>
#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;
static int itoa(int val, char* buf) {
const unsigned int radix = 10;
char* p;
unsigned int a; //every digit
int len;
char* b; //start of the digit char
char temp;
unsigned int u;
p = buf;
if (val < 0) {
*p++ = '-';
val = 0 - val;
}
u = (unsigned int)val;
b = p;
do {
a = u % radix;
u /= radix;
*p++ = a + '0';
} while (u > 0);
len = (int)(p - buf);
*p-- = 0;
//swap
do {
temp = *p;
*p = *b;
*b = temp;
--p;
++b;
} while (b < p);
return len;
}
static void fill_buf_impl(char* buf, char* prefix, size_t prefix_len, int value) {
memcpy(buf, prefix, prefix_len);
int len = itoa(value, buf + prefix_len);
buf[prefix_len + len] = '\0';
}
#define fill_buf(buf, str, value) fill_buf_impl(buf, str, sizeof(str) - 1, value);
int get_bulk() {
int n = 100;
Result = malloc(n * sizeof(struct Data));
for (int i=0; i<n; i++) {
int i_label = i + 1;
fill_buf(Result[i].port, "PORT ", i_label);
Result[i].status = i;
fill_buf(Result[i].value, "VALUE FOR ", i_label);
}
return n;
}
struct Data *get_next_result() {
return &Result[current_result++];
}
void free_result() {
free(Result);
current_result = 0;
}
#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;
}
#include <charconv>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <cassert>
namespace {
struct Data {
char port[20];
int status;
char value[256];
};
struct Data *Result = nullptr;
int current_result = 0;
template <size_t N, size_t M>
size_t format_prefix(char (&buf)[N], const char (&prefix)[M], int value) {
constexpr size_t kPrefixSizeWithoutNul = M - 1;
std::memcpy(buf, prefix, kPrefixSizeWithoutNul);
auto [ptr, errc] = std::to_chars(buf + kPrefixSizeWithoutNul, buf + N, value);
assert(errc == std::errc{});
*ptr++ = '\0';
return ptr - buf;
}
} // namespace
extern "C" int get_bulk() {
int n = 100;
Result = (Data*)std::malloc(sizeof(Data) * n);
for (int i = 0; i < n; i++) {
int i_label = i + 1;
Result[i].status = i;
format_prefix(Result[i].port, "PORT ", i_label);
format_prefix(Result[i].value, "VALUE FOR ", i_label);
}
return n;
}
extern "C" Data *get_next_result() {
return &Result[current_result++];
}
extern "C" void free_result() {
std::free(Result);
current_result = 0;
}
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "driver_rs"
version = "0.1.0"
[package]
name = "driver_rs"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
bench = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
/* 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();
}
#!/usr/bin/env bash
case $1 in
pack)
PACKED=`echo $2 | sed -s 's@/@%@g'`
mv $2 $PACKED
;;
unpack)
UNPACKED=`echo $2 | sed -s 's@%@/@g'`
mkdir -p `dirname $UNPACKED`
mv $2 $UNPACKED
;;
esac
flatten_dirs:
find . -type f -wholename '*/*' -not -path './.git/*' | sed 's@^\./@@g' | grep / | xargs -n1 ./gistify.sh pack
fold_dirs:
ls | grep '%' | xargs -n1 ./gistify.sh unpack
driver_c:
$(CC) -c driver.c -o driver-c.o -Wall -Werror -fpic -std=c17 -O3
$(CC) driver-c.o -o libmydriver-c.so -shared -O3
rm driver-c.o
driver_c_fast:
$(CC) -c driver-fast.c -o driver-c-fast.o -Wall -Werror -fpic -std=c17 -O3
$(CC) driver-c-fast.o -o libmydriver-c-fast.so -shared -O3
rm driver-c-fast.o
driver_cpp:
$(CXX) -c driver.cpp -o driver-cxx.o -Wall -Werror -fpic -std=c++20 -O3
$(CXX) driver-cxx.o -o libmydriver-cpp.so -shared -O3
rm driver-cxx.o
driver_rust:
cd driver_rs && cargo build --release
cp driver_rs/target/release/libdriver_rs.so libmydriver-rs.so
bench:
cd benchmark && cargo build --release
clean:
rm *.so
rm -rf driver_rs/target
rm -rf benchmark/target
rm -rf perf/data
build: driver_rust driver_c driver_c_fast driver_cpp bench
versions:
$(CXX) --version
$(CC) --version
rustc --version
hyperfine: build versions
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'
perf_record: build
mkdir -p perf/data
perf record -o perf/data/c -g --call-graph=dwarf -F500 -- benchmark/target/release/benchmark ./libmydriver-c.so
perf record -o perf/data/c-fast -g --call-graph=dwarf -F500 -- benchmark/target/release/benchmark ./libmydriver-c-fast.so
perf record -o perf/data/cpp -g --call-graph=dwarf -F500 -- benchmark/target/release/benchmark ./libmydriver-cpp.so
perf record -o perf/data/rs -g --call-graph=dwarf -F500 -- benchmark/target/release/benchmark ./libmydriver-rs.so
perf_flamegraph: perf_record
mkdir -p perf/svg
perf script -i perf/data/c | stackcollapse-perf.pl | flamegraph.pl > perf/svg/c.svg
perf script -i perf/data/c-fast | stackcollapse-perf.pl | flamegraph.pl > perf/svg/c-fast.svg
perf script -i perf/data/cpp | stackcollapse-perf.pl | flamegraph.pl > perf/svg/cpp.svg
perf script -i perf/data/rs | stackcollapse-perf.pl | flamegraph.pl > perf/svg/rs.svg
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment