diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2022-08-28 12:21:49 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-28 12:21:49 +0530 |
commit | d8396225c4287c53dd42226266e9f66983125e51 (patch) | |
tree | 27516437c40b80596f4d0fc9b821882a0f2a0ca1 | |
parent | 7c4f57e8b091bc386242f83b5373e4bb33382012 (diff) |
perf: use fast api for op_now (#15643)
-rw-r--r-- | cli/bench/op_now.js | 19 | ||||
-rw-r--r-- | ext/web/02_timers.js | 9 | ||||
-rw-r--r-- | ext/web/benches/timers_ops.rs | 10 | ||||
-rw-r--r-- | ext/web/lib.rs | 2 | ||||
-rw-r--r-- | ext/web/timers.rs | 32 | ||||
-rw-r--r-- | ops/lib.rs | 30 |
6 files changed, 80 insertions, 22 deletions
diff --git a/cli/bench/op_now.js b/cli/bench/op_now.js new file mode 100644 index 000000000..edce96fed --- /dev/null +++ b/cli/bench/op_now.js @@ -0,0 +1,19 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +const queueMicrotask = globalThis.queueMicrotask || process.nextTick; +let [total, count] = typeof Deno !== "undefined" + ? Deno.args + : [process.argv[2], process.argv[3]]; + +total = total ? parseInt(total, 0) : 50; +count = count ? parseInt(count, 10) : 10000000; + +function bench(fun) { + const start = Date.now(); + for (let i = 0; i < count; i++) fun(); + const elapsed = Date.now() - start; + const rate = Math.floor(count / (elapsed / 1000)); + console.log(`time ${elapsed} ms rate ${rate}`); + if (--total) queueMicrotask(() => bench(fun)); +} + +bench(() => performance.now()); diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index 07b9587ea..f703120e6 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -13,6 +13,7 @@ MapPrototypeGet, MapPrototypeHas, MapPrototypeSet, + Uint32Array, // deno-lint-ignore camelcase NumberPOSITIVE_INFINITY, PromisePrototypeThen, @@ -25,8 +26,14 @@ const { reportException } = window.__bootstrap.event; const { assert } = window.__bootstrap.infra; + let hr; function opNow() { - return ops.op_now(); + if (!hr) { + hr = new Uint32Array(2); + ops.op_now_set_buf(hr); + } + ops.op_now.fast(); + return (hr[0] * 1000 + hr[1] / 1e6); } // --------------------------------------------------------------------------- diff --git a/ext/web/benches/timers_ops.rs b/ext/web/benches/timers_ops.rs index f5d03d6c3..10b434015 100644 --- a/ext/web/benches/timers_ops.rs +++ b/ext/web/benches/timers_ops.rs @@ -1,8 +1,8 @@ use deno_core::Extension; +use deno_bench_util::bench_js_async; use deno_bench_util::bench_or_profile; use deno_bench_util::bencher::{benchmark_group, Bencher}; -use deno_bench_util::{bench_js_async, bench_js_sync}; use deno_web::BlobStore; struct Permissions; @@ -27,7 +27,7 @@ fn setup() -> Vec<Extension> { Extension::builder() .js(vec![ ("setup", r#" - const { opNow, setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; + const { setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; Deno.core.setMacrotaskCallback(handleTimerMacrotask); "#), ]) @@ -39,13 +39,9 @@ fn setup() -> Vec<Extension> { ] } -fn bench_op_now(b: &mut Bencher) { - bench_js_sync(b, r#"opNow();"#, setup); -} - fn bench_set_timeout(b: &mut Bencher) { bench_js_async(b, r#"setTimeout(() => {}, 0);"#, setup); } -benchmark_group!(benches, bench_op_now, bench_set_timeout,); +benchmark_group!(benches, bench_set_timeout,); bench_or_profile!(benches); diff --git a/ext/web/lib.rs b/ext/web/lib.rs index a80447614..91c680f5f 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -51,6 +51,7 @@ pub use crate::message_port::JsMessageData; pub use crate::message_port::MessagePort; use crate::timers::op_now; +use crate::timers::op_now_set_buf; use crate::timers::op_sleep; use crate::timers::op_timer_handle; use crate::timers::StartTime; @@ -106,6 +107,7 @@ pub fn init<P: TimersPermission + 'static>( compression::op_compression_new::decl(), compression::op_compression_write::decl(), compression::op_compression_finish::decl(), + op_now_set_buf::decl(), op_now::decl::<P>(), op_timer_handle::decl(), op_cancel_handle::decl(), diff --git a/ext/web/timers.rs b/ext/web/timers.rs index 0f781a579..d9ceef875 100644 --- a/ext/web/timers.rs +++ b/ext/web/timers.rs @@ -4,6 +4,7 @@ use deno_core::error::AnyError; use deno_core::op; +use deno_core::ZeroCopyBuf; use deno_core::CancelFuture; use deno_core::CancelHandle; @@ -23,29 +24,50 @@ pub trait TimersPermission { pub type StartTime = Instant; +static mut NOW_BUF: *mut u32 = std::ptr::null_mut(); + +#[op] +pub fn op_now_set_buf(buf: ZeroCopyBuf) { + assert_eq!(buf.len(), 8); + // SAFETY: This is safe because this is the only place where we initialize + // NOW_BUF. + unsafe { + NOW_BUF = buf.as_ptr() as *mut u32; + } +} + // Returns a milliseconds and nanoseconds subsec // since the start time of the deno runtime. // If the High precision flag is not set, the // nanoseconds are rounded on 2ms. -#[op] -pub fn op_now<TP>(state: &mut OpState) -> f64 +#[op(fast)] +pub fn op_now<TP>(state: &mut OpState) where TP: TimersPermission + 'static, { let start_time = state.borrow::<StartTime>(); let elapsed = start_time.elapsed(); let seconds = elapsed.as_secs(); - let mut subsec_nanos = elapsed.subsec_nanos() as f64; - let reduced_time_precision = 2_000_000.0; // 2ms in nanoseconds + let mut subsec_nanos = elapsed.subsec_nanos(); // If the permission is not enabled // Round the nano result on 2 milliseconds // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision if !state.borrow_mut::<TP>().allow_hrtime() { + let reduced_time_precision = 2_000_000; // 2ms in nanoseconds subsec_nanos -= subsec_nanos % reduced_time_precision; } - (seconds * 1_000) as f64 + (subsec_nanos / 1_000_000.0) + // SAFETY: This is safe because we initialize NOW_BUF in op_now_set_buf, its a null pointer + // otherwise. + // op_now_set_buf guarantees that the buffer is 8 bytes long. + unsafe { + if !NOW_BUF.is_null() { + let buf = std::slice::from_raw_parts_mut(NOW_BUF, 2); + buf[0] = seconds as u32; + buf[1] = subsec_nanos as u32; + } + } } pub struct TimerHandle(Rc<CancelHandle>); diff --git a/ops/lib.rs b/ops/lib.rs index 2028fc875..500e1f5f9 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -365,15 +365,32 @@ fn codegen_fast_impl( } }, quote! { - #func_name #ty_generics as *const _ + #func_name::<#type_params> as *const _ }, ) }; + + let fast_struct = format_ident!("fast_{}", name); + let (type_params, ty_generics, struct_generics) = + if type_params.is_empty() { + (quote! { () }, quote! {}, quote! {}) + } else { + ( + quote! { #type_params }, + quote! { #ty_generics }, + quote! { ::<#type_params> }, + ) + }; return ( quote! { + #[allow(non_camel_case_types)] + #[doc(hidden)] + struct #fast_struct #ty_generics { + _phantom: ::std::marker::PhantomData<#type_params>, + } #trampoline - impl #impl_generics #core::v8::fast_api::FastFunction for #name #ty_generics { - fn function(&self) -> *const ::std::ffi::c_void { + impl #impl_generics #core::v8::fast_api::FastFunction for #fast_struct #ty_generics #where_clause { + fn function(&self) -> *const ::std::ffi::c_void { #raw_block } fn args(&self) -> &'static [#core::v8::fast_api::Type] { @@ -384,7 +401,7 @@ fn codegen_fast_impl( } } }, - quote! { Some(Box::new(#name #ty_generics)) }, + quote! { Some(Box::new(#fast_struct #struct_generics { _phantom: ::std::marker::PhantomData })) }, ); } } @@ -439,11 +456,6 @@ struct FastApiSyn { } fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> { - // TODO(@littledivy): Support generics - if !f.sig.generics.params.is_empty() { - return None; - } - let inputs = &f.sig.inputs; let ret = match &f.sig.output { syn::ReturnType::Default => quote!(#core::v8::fast_api::CType::Void), |