Merge pull request #3407 from rtfeldman/dylib-roc-benchmarks

Dylib roc benchmarks
This commit is contained in:
Richard Feldman 2022-07-06 21:19:29 -04:00 committed by GitHub
commit 85d87ab431
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 330 additions and 129 deletions

5
.gitignore vendored
View file

@ -47,3 +47,8 @@ roc_linux_x86_64.tar.gz
# macOS .DS_Store files
.DS_Store
# files geneated when formatting fails
*.roc-format-failed
*.roc-format-failed-ast-after
*.roc-format-failed-ast-before

60
Cargo.lock generated
View file

@ -524,7 +524,7 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"criterion",
"rlimit",
"roc_cli",
"roc_collections",
@ -916,32 +916,6 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "criterion"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
dependencies = [
"atty",
"cast",
"clap 2.34.0",
"criterion-plot 0.4.4",
"csv",
"itertools 0.10.3",
"lazy_static",
"num-traits",
"oorandom",
"plotters 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion"
version = "0.3.5"
@ -950,13 +924,13 @@ dependencies = [
"atty",
"cast",
"clap 2.34.0",
"criterion-plot 0.4.3",
"criterion-plot",
"csv",
"itertools 0.10.3",
"lazy_static",
"num-traits",
"oorandom",
"plotters 0.3.1 (git+https://github.com/Anton-4/plotters)",
"plotters",
"rayon",
"regex",
"serde",
@ -976,16 +950,6 @@ dependencies = [
"itertools 0.9.0",
]
[[package]]
name = "criterion-plot"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
dependencies = [
"cast",
"itertools 0.10.3",
]
[[package]]
name = "crossbeam"
version = "0.8.1"
@ -2893,19 +2857,6 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "plotters"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters"
version = "0.3.1"
@ -3471,7 +3422,7 @@ dependencies = [
"clap 3.2.8",
"cli_utils",
"const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"criterion",
"errno",
"indoc",
"libc",
@ -3877,7 +3828,7 @@ name = "roc_parse"
version = "0.1.0"
dependencies = [
"bumpalo",
"criterion 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"criterion",
"encode_unicode",
"indoc",
"pretty_assertions",
@ -4762,6 +4713,7 @@ name = "test_gen"
version = "0.1.0"
dependencies = [
"bumpalo",
"criterion",
"either",
"indoc",
"inkwell 0.1.0",

View file

@ -3525,15 +3525,31 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
let it = args.iter().zip(roc_function.get_type().get_param_types());
for (arg, fastcc_type) in it {
let it = args
.iter()
.zip(roc_function.get_type().get_param_types())
.zip(arguments);
for ((arg, fastcc_type), layout) in it {
let arg_type = arg.get_type();
if arg_type == fastcc_type {
// the C and Fast calling conventions agree
arguments_for_call.push(*arg);
} else {
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
arguments_for_call.push(cast);
match layout {
Layout::Builtin(Builtin::List(_)) => {
let loaded = env
.builder
.build_load(arg.into_pointer_value(), "load_list_pointer");
let cast =
complex_bitcast_check_size(env, loaded, fastcc_type, "to_fastcc_type_1");
arguments_for_call.push(cast);
}
_ => {
let cast =
complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type_1");
arguments_for_call.push(cast);
}
}
}
}
@ -3657,7 +3673,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// the C and Fast calling conventions agree
*arg
} else {
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type")
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type_2")
}
});

View file

@ -12,6 +12,26 @@ pub struct RocCallResult<T> {
value: MaybeUninit<T>,
}
impl<T> RocCallResult<T> {
pub fn new(value: T) -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(value),
}
}
}
impl<T: Default> Default for RocCallResult<T> {
fn default() -> Self {
Self {
tag: 0,
error_msg: std::ptr::null_mut(),
value: MaybeUninit::new(Default::default()),
}
}
}
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
fn from(call_result: RocCallResult<T>) -> Self {
match call_result.tag {
@ -30,6 +50,29 @@ impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
}
}
#[macro_export]
macro_rules! run_roc_dylib {
($lib:expr, $main_fn_name:expr, $argument_type:ty, $return_type:ty, $errors:expr) => {{
use inkwell::context::Context;
use roc_builtins::bitcode;
use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit;
// NOTE: return value is not first argument currently (may/should change in the future)
type Main = unsafe extern "C" fn($argument_type, *mut RocCallResult<$return_type>);
unsafe {
let main: libloading::Symbol<Main> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
main
}
}};
}
#[macro_export]
macro_rules! run_jit_function {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{

View file

@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
[dev-dependencies]
criterion = { version = "0.3.5", features = ["html_reports"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"

View file

@ -43,6 +43,7 @@ libloading = "0.7.1"
wasmer-wasi = "2.2.1"
tempfile = "3.2.0"
indoc = "1.0.3"
criterion = { git = "https://github.com/Anton-4/criterion.rs" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
@ -57,3 +58,7 @@ gen-llvm = []
gen-dev = []
gen-wasm = []
wasm-cli-run = []
[[bench]]
name = "list_map"
harness = false

View file

@ -0,0 +1,119 @@
#[path = "../src/helpers/mod.rs"]
mod helpers;
// defines roc_alloc and friends
pub use helpers::platform_functions::*;
use bumpalo::Bump;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use roc_gen_llvm::{run_roc::RocCallResult, run_roc_dylib};
use roc_mono::ir::OptLevel;
use roc_std::RocList;
// results July 6th, 2022
//
// roc sum map time: [612.73 ns 614.24 ns 615.98 ns]
// roc sum map_with_index time: [5.3177 us 5.3218 us 5.3255 us]
// rust (debug) time: [24.081 us 24.163 us 24.268 us]
type Input = RocList<i64>;
type Output = i64;
type Main<I, O> = unsafe extern "C" fn(I, *mut RocCallResult<O>);
const ROC_LIST_MAP: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main = \list ->
list
|> List.map (\x -> x + 2)
|> List.len
"#
);
const ROC_LIST_MAP_WITH_INDEX: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> Nat
main = \list ->
list
|> List.mapWithIndex (\x, _ -> x + 2)
|> List.len
"#
);
fn roc_function<'a, 'b>(
arena: &'a Bump,
source: &str,
) -> libloading::Symbol<'a, Main<&'b Input, Output>> {
let config = helpers::llvm::HelperConfig {
is_gen_test: true,
ignore_problems: false,
add_debug_info: true,
opt_level: OptLevel::Optimize,
};
let context = inkwell::context::Context::create();
let (main_fn_name, errors, lib) =
helpers::llvm::helper(arena, config, source, arena.alloc(context));
assert!(errors.is_empty(), "Encountered errors:\n{}", errors);
run_roc_dylib!(arena.alloc(lib), main_fn_name, &Input, Output, errors)
}
fn rust_main(argument: &RocList<i64>, output: &mut RocCallResult<i64>) {
let mut answer = 0;
for x in argument.iter() {
answer += x;
}
*output = RocCallResult::new(answer);
}
fn create_input_list() -> RocList<i64> {
let numbers = Vec::from_iter(0..1_000);
RocList::from_slice(&numbers)
}
pub fn criterion_benchmark(c: &mut Criterion) {
let arena = Bump::new();
let list_map_with_index_main = roc_function(&arena, ROC_LIST_MAP_WITH_INDEX);
let list_map_main = roc_function(&arena, ROC_LIST_MAP);
let input = &*arena.alloc(create_input_list());
c.bench_function("roc sum map", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
list_map_main(black_box(input), &mut main_result);
})
});
c.bench_function("roc sum map_with_index", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
list_map_with_index_main(black_box(input), &mut main_result);
})
});
let input = &*arena.alloc(create_input_list());
c.bench_function("rust", |b| {
b.iter(|| {
let mut main_result = RocCallResult::default();
rust_main(black_box(input), &mut main_result);
})
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View file

@ -246,39 +246,84 @@ fn create_llvm_module<'a>(
(main_fn_name, delayed_errors.join("\n"), env.module)
}
#[derive(Debug, Clone, Copy)]
pub struct HelperConfig {
pub is_gen_test: bool,
pub ignore_problems: bool,
pub add_debug_info: bool,
pub opt_level: OptLevel,
}
#[allow(dead_code)]
#[inline(never)]
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
config: HelperConfig,
src: &str,
is_gen_test: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) {
let target = target_lexicon::Triple::host();
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let (main_fn_name, delayed_errors, module) = create_llvm_module(
arena,
src,
is_gen_test,
ignore_problems,
config.is_gen_test,
config.ignore_problems,
context,
&target,
opt_level,
config.opt_level,
);
let lib = llvm_module_to_dylib(module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let res_lib = if config.add_debug_info {
let module = annotate_with_debug_info(module, context);
llvm_module_to_dylib(&module, &target, config.opt_level)
} else {
llvm_module_to_dylib(module, &target, config.opt_level)
};
let lib = res_lib.expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib)
}
fn annotate_with_debug_info<'ctx>(
module: &Module<'ctx>,
context: &'ctx inkwell::context::Context,
) -> Module<'ctx> {
use std::process::Command;
let app_ll_file = "/tmp/roc-debugir.ll";
let app_dbg_ll_file = "/tmp/roc-debugir.dbg.ll";
let app_bc_file = "/tmp/roc-debugir.bc";
// write the ll code to a file, so we can modify it
module.print_to_file(&app_ll_file).unwrap();
// run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir")
.args(&["-instnamer", app_ll_file])
.output()
{
Ok(_) => {}
Err(error) => {
use std::io::ErrorKind;
match error.kind() {
ErrorKind::NotFound => panic!(
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
),
_ => panic!("{:?}", error),
}
}
}
Command::new("llvm-as")
.args(&[app_dbg_ll_file, "-o", app_bc_file])
.output()
.unwrap();
inkwell::module::Module::parse_bitcode_from_path(&app_bc_file, context).unwrap()
}
fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat};
@ -513,13 +558,26 @@ macro_rules! assert_llvm_evals_to {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen_llvm::run_jit_function;
use roc_mono::ir::OptLevel;
let arena = Bump::new();
let context = Context::create();
let is_gen_test = true;
let opt_level = if cfg!(debug_assertions) {
OptLevel::Normal
} else {
OptLevel::Optimize
};
let config = $crate::helpers::llvm::HelperConfig {
is_gen_test: true,
add_debug_info: false,
ignore_problems: $ignore_problems,
opt_level,
};
let (main_fn_name, errors, lib) =
$crate::helpers::llvm::helper(&arena, $src, is_gen_test, $ignore_problems, &context);
$crate::helpers::llvm::helper(&arena, config, $src, &context);
let transform = |success| {
let expected = $expected;

View file

@ -1,5 +1,7 @@
extern crate bumpalo;
pub mod platform_functions;
#[cfg(feature = "gen-dev")]
pub mod dev;
pub mod from_wasmer_memory;

View file

@ -0,0 +1,54 @@
use core::ffi::c_void;
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
libc::memcpy(dest, src, bytes)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}

View file

@ -22,57 +22,4 @@ pub mod wasm_str;
#[cfg(feature = "gen-wasm")]
pub mod wasm_linking;
use core::ffi::c_void;
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
libc::memcpy(dest, src, bytes)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}
pub use helpers::platform_functions::*;