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 # macOS .DS_Store files
.DS_Store .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 = [ dependencies = [
"bumpalo", "bumpalo",
"const_format", "const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "criterion",
"rlimit", "rlimit",
"roc_cli", "roc_cli",
"roc_collections", "roc_collections",
@ -916,32 +916,6 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.5" version = "0.3.5"
@ -950,13 +924,13 @@ dependencies = [
"atty", "atty",
"cast", "cast",
"clap 2.34.0", "clap 2.34.0",
"criterion-plot 0.4.3", "criterion-plot",
"csv", "csv",
"itertools 0.10.3", "itertools 0.10.3",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"oorandom", "oorandom",
"plotters 0.3.1 (git+https://github.com/Anton-4/plotters)", "plotters",
"rayon", "rayon",
"regex", "regex",
"serde", "serde",
@ -976,16 +950,6 @@ dependencies = [
"itertools 0.9.0", "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]] [[package]]
name = "crossbeam" name = "crossbeam"
version = "0.8.1" version = "0.8.1"
@ -2893,19 +2857,6 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 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]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.1" version = "0.3.1"
@ -3471,7 +3422,7 @@ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"cli_utils", "cli_utils",
"const_format", "const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "criterion",
"errno", "errno",
"indoc", "indoc",
"libc", "libc",
@ -3877,7 +3828,7 @@ name = "roc_parse"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"criterion 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "criterion",
"encode_unicode", "encode_unicode",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
@ -4762,6 +4713,7 @@ name = "test_gen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"criterion",
"either", "either",
"indoc", "indoc",
"inkwell 0.1.0", "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 mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
let it = args.iter().zip(roc_function.get_type().get_param_types()); let it = args
for (arg, fastcc_type) in it { .iter()
.zip(roc_function.get_type().get_param_types())
.zip(arguments);
for ((arg, fastcc_type), layout) in it {
let arg_type = arg.get_type(); let arg_type = arg.get_type();
if arg_type == fastcc_type { if arg_type == fastcc_type {
// the C and Fast calling conventions agree // the C and Fast calling conventions agree
arguments_for_call.push(*arg); arguments_for_call.push(*arg);
} else { } else {
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); match layout {
arguments_for_call.push(cast); 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 // the C and Fast calling conventions agree
*arg *arg
} else { } 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>, 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> { impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
fn from(call_result: RocCallResult<T>) -> Self { fn from(call_result: RocCallResult<T>) -> Self {
match call_result.tag { 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_export]
macro_rules! run_jit_function { macro_rules! run_jit_function {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ ($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" encode_unicode = "0.3.6"
[dev-dependencies] [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" pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
quickcheck = "1.0.3" quickcheck = "1.0.3"

View file

@ -43,6 +43,7 @@ libloading = "0.7.1"
wasmer-wasi = "2.2.1" wasmer-wasi = "2.2.1"
tempfile = "3.2.0" tempfile = "3.2.0"
indoc = "1.0.3" indoc = "1.0.3"
criterion = { git = "https://github.com/Anton-4/criterion.rs" }
# Wasmer singlepass compiler only works on x86_64. # Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies] [target.'cfg(target_arch = "x86_64")'.dev-dependencies]
@ -57,3 +58,7 @@ gen-llvm = []
gen-dev = [] gen-dev = []
gen-wasm = [] gen-wasm = []
wasm-cli-run = [] 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) (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)] #[allow(dead_code)]
#[inline(never)] #[inline(never)]
pub fn helper<'a>( pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
config: HelperConfig,
src: &str, src: &str,
is_gen_test: bool,
ignore_problems: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { ) -> (&'static str, String, Library) {
let target = target_lexicon::Triple::host(); 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( let (main_fn_name, delayed_errors, module) = create_llvm_module(
arena, arena,
src, src,
is_gen_test, config.is_gen_test,
ignore_problems, config.ignore_problems,
context, context,
&target, &target,
opt_level, config.opt_level,
); );
let lib = llvm_module_to_dylib(module, &target, opt_level) let res_lib = if config.add_debug_info {
.expect("Error loading compiled dylib for test"); 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) (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 { fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat}; use target_lexicon::{Architecture, BinaryFormat};
@ -513,13 +558,26 @@ macro_rules! assert_llvm_evals_to {
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use roc_gen_llvm::run_jit_function; use roc_gen_llvm::run_jit_function;
use roc_mono::ir::OptLevel;
let arena = Bump::new(); let arena = Bump::new();
let context = Context::create(); 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) = 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 transform = |success| {
let expected = $expected; let expected = $expected;

View file

@ -1,5 +1,7 @@
extern crate bumpalo; extern crate bumpalo;
pub mod platform_functions;
#[cfg(feature = "gen-dev")] #[cfg(feature = "gen-dev")]
pub mod dev; pub mod dev;
pub mod from_wasmer_memory; 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")] #[cfg(feature = "gen-wasm")]
pub mod wasm_linking; pub mod wasm_linking;
use core::ffi::c_void; pub use helpers::platform_functions::*;
/// # 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!(),
}
}