1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00

perf(ops): inline &[u8] arguments and enable fast API (#15731)

This commit is contained in:
Divy Srivastava 2022-09-07 16:21:47 +05:30 committed by GitHub
parent d57f9d560d
commit 027d4d433d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 128 additions and 60 deletions

View file

@ -134,12 +134,12 @@ impl Resource for WasmStreamingResource {
pub fn op_wasm_streaming_feed( pub fn op_wasm_streaming_feed(
state: &mut OpState, state: &mut OpState,
rid: ResourceId, rid: ResourceId,
bytes: ZeroCopyBuf, bytes: &[u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
let wasm_streaming = let wasm_streaming =
state.resource_table.get::<WasmStreamingResource>(rid)?; state.resource_table.get::<WasmStreamingResource>(rid)?;
wasm_streaming.0.borrow_mut().on_bytes_received(&bytes); wasm_streaming.0.borrow_mut().on_bytes_received(bytes);
Ok(()) Ok(())
} }

View file

@ -231,7 +231,7 @@ fn op_encode<'a>(
#[op(v8)] #[op(v8)]
fn op_decode<'a>( fn op_decode<'a>(
scope: &mut v8::HandleScope<'a>, scope: &mut v8::HandleScope<'a>,
zero_copy: ZeroCopyBuf, zero_copy: &[u8],
) -> Result<serde_v8::Value<'a>, Error> { ) -> Result<serde_v8::Value<'a>, Error> {
let buf = &zero_copy; let buf = &zero_copy;

View file

@ -18,7 +18,6 @@ use deno_core::Extension;
use deno_core::OpState; use deno_core::OpState;
use deno_core::Resource; use deno_core::Resource;
use deno_core::ResourceId; use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use dlopen::raw::Library; use dlopen::raw::Library;
use libffi::middle::Arg; use libffi::middle::Arg;
use libffi::middle::Cif; use libffi::middle::Cif;
@ -2154,7 +2153,7 @@ where
fn op_ffi_buf_copy_into<FP>( fn op_ffi_buf_copy_into<FP>(
state: &mut deno_core::OpState, state: &mut deno_core::OpState,
src: usize, src: usize,
mut dst: ZeroCopyBuf, dst: &mut [u8],
len: usize, len: usize,
) -> Result<(), AnyError> ) -> Result<(), AnyError>
where where

View file

@ -13,6 +13,7 @@
MapPrototypeGet, MapPrototypeGet,
MapPrototypeHas, MapPrototypeHas,
MapPrototypeSet, MapPrototypeSet,
Uint8Array,
Uint32Array, Uint32Array,
// deno-lint-ignore camelcase // deno-lint-ignore camelcase
NumberPOSITIVE_INFINITY, NumberPOSITIVE_INFINITY,
@ -27,13 +28,10 @@
const { reportException } = window.__bootstrap.event; const { reportException } = window.__bootstrap.event;
const { assert } = window.__bootstrap.infra; const { assert } = window.__bootstrap.infra;
let hr; const hrU8 = new Uint8Array(8);
const hr = new Uint32Array(hrU8.buffer);
function opNow() { function opNow() {
if (!hr) { ops.op_now.fast(hrU8);
hr = new Uint32Array(2);
ops.op_now_set_buf(hr);
}
ops.op_now.fast();
return (hr[0] * 1000 + hr[1] / 1e6); return (hr[0] * 1000 + hr[1] / 1e6);
} }

View file

@ -68,38 +68,38 @@ pub fn op_compression_new(
pub fn op_compression_write( pub fn op_compression_write(
state: &mut OpState, state: &mut OpState,
rid: ResourceId, rid: ResourceId,
input: ZeroCopyBuf, input: &[u8],
) -> Result<ZeroCopyBuf, AnyError> { ) -> Result<ZeroCopyBuf, AnyError> {
let resource = state.resource_table.get::<CompressionResource>(rid)?; let resource = state.resource_table.get::<CompressionResource>(rid)?;
let mut inner = resource.0.borrow_mut(); let mut inner = resource.0.borrow_mut();
let out: Vec<u8> = match &mut *inner { let out: Vec<u8> = match &mut *inner {
Inner::DeflateDecoder(d) => { Inner::DeflateDecoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }
Inner::DeflateEncoder(d) => { Inner::DeflateEncoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }
Inner::DeflateRawDecoder(d) => { Inner::DeflateRawDecoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }
Inner::DeflateRawEncoder(d) => { Inner::DeflateRawEncoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }
Inner::GzDecoder(d) => { Inner::GzDecoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }
Inner::GzEncoder(d) => { Inner::GzEncoder(d) => {
d.write_all(&input)?; d.write_all(input)?;
d.flush()?; d.flush()?;
d.get_mut().drain(..) d.get_mut().drain(..)
} }

View file

@ -50,7 +50,6 @@ pub use crate::message_port::JsMessageData;
pub use crate::message_port::MessagePort; pub use crate::message_port::MessagePort;
use crate::timers::op_now; use crate::timers::op_now;
use crate::timers::op_now_set_buf;
use crate::timers::op_sleep; use crate::timers::op_sleep;
use crate::timers::op_timer_handle; use crate::timers::op_timer_handle;
use crate::timers::StartTime; use crate::timers::StartTime;
@ -106,7 +105,6 @@ pub fn init<P: TimersPermission + 'static>(
compression::op_compression_new::decl(), compression::op_compression_new::decl(),
compression::op_compression_write::decl(), compression::op_compression_write::decl(),
compression::op_compression_finish::decl(), compression::op_compression_finish::decl(),
op_now_set_buf::decl(),
op_now::decl::<P>(), op_now::decl::<P>(),
op_timer_handle::decl(), op_timer_handle::decl(),
op_cancel_handle::decl(), op_cancel_handle::decl(),
@ -149,8 +147,8 @@ fn forgiving_base64_decode(input: &mut [u8]) -> Result<usize, AnyError> {
} }
#[op] #[op]
fn op_base64_encode(s: ZeroCopyBuf) -> String { fn op_base64_encode(s: &[u8]) -> String {
forgiving_base64_encode(s.as_ref()) forgiving_base64_encode(s)
} }
#[op] #[op]
@ -179,7 +177,7 @@ fn op_encoding_normalize_label(label: String) -> Result<String, AnyError> {
#[op] #[op]
fn op_encoding_decode_single( fn op_encoding_decode_single(
data: ZeroCopyBuf, data: &[u8],
label: String, label: String,
fatal: bool, fatal: bool,
ignore_bom: bool, ignore_bom: bool,
@ -205,7 +203,7 @@ fn op_encoding_decode_single(
if fatal { if fatal {
let (result, _, written) = let (result, _, written) =
decoder.decode_to_utf16_without_replacement(&data, &mut output, true); decoder.decode_to_utf16_without_replacement(data, &mut output, true);
match result { match result {
DecoderResult::InputEmpty => { DecoderResult::InputEmpty => {
output.truncate(written); output.truncate(written);
@ -220,7 +218,7 @@ fn op_encoding_decode_single(
} }
} else { } else {
let (result, _, written, _) = let (result, _, written, _) =
decoder.decode_to_utf16(&data, &mut output, true); decoder.decode_to_utf16(data, &mut output, true);
match result { match result {
CoderResult::InputEmpty => { CoderResult::InputEmpty => {
output.truncate(written); output.truncate(written);
@ -262,7 +260,7 @@ fn op_encoding_new_decoder(
#[op] #[op]
fn op_encoding_decode( fn op_encoding_decode(
state: &mut OpState, state: &mut OpState,
data: ZeroCopyBuf, data: &[u8],
rid: ResourceId, rid: ResourceId,
stream: bool, stream: bool,
) -> Result<U16String, AnyError> { ) -> Result<U16String, AnyError> {
@ -279,7 +277,7 @@ fn op_encoding_decode(
if fatal { if fatal {
let (result, _, written) = let (result, _, written) =
decoder.decode_to_utf16_without_replacement(&data, &mut output, !stream); decoder.decode_to_utf16_without_replacement(data, &mut output, !stream);
match result { match result {
DecoderResult::InputEmpty => { DecoderResult::InputEmpty => {
output.truncate(written); output.truncate(written);
@ -294,7 +292,7 @@ fn op_encoding_decode(
} }
} else { } else {
let (result, _, written, _) = let (result, _, written, _) =
decoder.decode_to_utf16(&data, &mut output, !stream); decoder.decode_to_utf16(data, &mut output, !stream);
match result { match result {
CoderResult::InputEmpty => { CoderResult::InputEmpty => {
output.truncate(written); output.truncate(written);
@ -326,7 +324,7 @@ struct EncodeIntoResult {
#[op] #[op]
fn op_encoding_encode_into( fn op_encoding_encode_into(
input: String, input: String,
mut buffer: ZeroCopyBuf, buffer: &mut [u8],
) -> EncodeIntoResult { ) -> EncodeIntoResult {
// Since `input` is already UTF-8, we can simply find the last UTF-8 code // Since `input` is already UTF-8, we can simply find the last UTF-8 code
// point boundary from input that fits in `buffer`, and copy the bytes up to // point boundary from input that fits in `buffer`, and copy the bytes up to

View file

@ -4,7 +4,6 @@
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op; use deno_core::op;
use deno_core::ZeroCopyBuf;
use deno_core::CancelFuture; use deno_core::CancelFuture;
use deno_core::CancelHandle; use deno_core::CancelHandle;
@ -24,24 +23,12 @@ pub trait TimersPermission {
pub type StartTime = Instant; 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 // Returns a milliseconds and nanoseconds subsec
// since the start time of the deno runtime. // since the start time of the deno runtime.
// If the High precision flag is not set, the // If the High precision flag is not set, the
// nanoseconds are rounded on 2ms. // nanoseconds are rounded on 2ms.
#[op(fast)] #[op(fast)]
pub fn op_now<TP>(state: &mut OpState) pub fn op_now<TP>(state: &mut OpState, buf: &mut [u8])
where where
TP: TimersPermission + 'static, TP: TimersPermission + 'static,
{ {
@ -57,18 +44,15 @@ where
let reduced_time_precision = 2_000_000; // 2ms in nanoseconds let reduced_time_precision = 2_000_000; // 2ms in nanoseconds
subsec_nanos -= subsec_nanos % reduced_time_precision; subsec_nanos -= subsec_nanos % reduced_time_precision;
} }
if buf.len() < 8 {
// SAFETY: This is safe because we initialize NOW_BUF in op_now_set_buf, its a null pointer return;
// otherwise. }
// op_now_set_buf guarantees that the buffer is 8 bytes long. let buf: &mut [u32] =
unsafe { // SAFETY: buffer is at least 8 bytes long.
if !NOW_BUF.is_null() { unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as _, 2) };
let buf = std::slice::from_raw_parts_mut(NOW_BUF, 2);
buf[0] = seconds as u32; buf[0] = seconds as u32;
buf[1] = subsec_nanos as u32; buf[1] = subsec_nanos as u32;
} }
}
}
pub struct TimerHandle(Rc<CancelHandle>); pub struct TimerHandle(Rc<CancelHandle>);

View file

@ -11,6 +11,7 @@ use quote::format_ident;
use quote::quote; use quote::quote;
use quote::ToTokens; use quote::ToTokens;
use regex::Regex; use regex::Regex;
use std::collections::HashMap;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::token::Comma; use syn::token::Comma;
use syn::FnArg; use syn::FnArg;
@ -310,10 +311,11 @@ fn codegen_fast_impl(
use_recv, use_recv,
use_fast_cb_opts, use_fast_cb_opts,
v8_values, v8_values,
slices,
}) = fast_info }) = fast_info
{ {
let offset = if use_recv { 1 } else { 0 }; let offset = if use_recv { 1 } else { 0 };
let inputs = &f let mut inputs = f
.sig .sig
.inputs .inputs
.iter() .iter()
@ -327,8 +329,11 @@ fn codegen_fast_impl(
_ => unreachable!(), _ => unreachable!(),
}, },
}; };
if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 { if let Some(ty) = slices.get(&(idx + offset)) {
return quote! { #ident: *mut #core::v8::fast_api::FastApiCallbackOptions }; return quote! { #ident: *const #core::v8::fast_api::FastApiTypedArray< #ty > };
}
if use_fast_cb_opts && idx + offset == f.sig.inputs.len() - 1 {
return quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions };
} }
if v8_values.contains(&idx) { if v8_values.contains(&idx) {
return quote! { #ident: #core::v8::Local < #core::v8::Value > }; return quote! { #ident: #core::v8::Local < #core::v8::Value > };
@ -336,6 +341,9 @@ fn codegen_fast_impl(
quote!(#arg) quote!(#arg)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !slices.is_empty() && !use_fast_cb_opts {
inputs.push(quote! { fast_api_callback_options: *mut #core::v8::fast_api::FastApiCallbackOptions });
}
let input_idents = f let input_idents = f
.sig .sig
.inputs .inputs
@ -349,8 +357,19 @@ fn codegen_fast_impl(
_ => unreachable!(), _ => unreachable!(),
}, },
}; };
if slices.get(&idx).is_some() {
return quote! {
match unsafe { &* #ident }.get_storage_if_aligned() {
Some(s) => s,
None => {
unsafe { &mut * fast_api_callback_options }.fallback = true;
return Default::default();
},
}
};
}
if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 { if use_fast_cb_opts && idx == f.sig.inputs.len() - 1 {
return quote! { Some(unsafe { &mut * #ident }) }; return quote! { Some(unsafe { &mut * fast_api_callback_options }) };
} }
if v8_values.contains(&idx) { if v8_values.contains(&idx) {
return quote! { return quote! {
@ -494,6 +513,7 @@ struct FastApiSyn {
use_recv: bool, use_recv: bool,
use_fast_cb_opts: bool, use_fast_cb_opts: bool,
v8_values: Vec<usize>, v8_values: Vec<usize>,
slices: HashMap<usize, TokenStream2>,
} }
fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> { fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
@ -509,11 +529,12 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
let mut use_recv = false; let mut use_recv = false;
let mut use_fast_cb_opts = false; let mut use_fast_cb_opts = false;
let mut v8_values = Vec::new(); let mut v8_values = Vec::new();
let mut slices = HashMap::new();
let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }]; let mut args = vec![quote! { #core::v8::fast_api::Type::V8Value }];
for (pos, input) in inputs.iter().enumerate() { for (pos, input) in inputs.iter().enumerate() {
if pos == inputs.len() - 1 && is_optional_fast_callback_option(input) { if pos == inputs.len() - 1 && is_optional_fast_callback_option(input) {
args.push(quote! { #core::v8::fast_api::Type::CallbackOptions });
use_fast_cb_opts = true; use_fast_cb_opts = true;
args.push(quote! { #core::v8::fast_api::Type::CallbackOptions });
continue; continue;
} }
@ -536,9 +557,15 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
Some(arg) => { Some(arg) => {
args.push(arg); args.push(arg);
} }
None => match is_ref_slice(&ty) {
Some(_) => {
args.push(quote! { #core::v8::fast_api::Type::TypedArray(#core::v8::fast_api::CType::Uint8) });
slices.insert(pos, quote!(u8));
}
// early return, this function cannot be a fast call. // early return, this function cannot be a fast call.
None => return None, None => return None,
}, },
},
Some(arg) => { Some(arg) => {
args.push(arg); args.push(arg);
} }
@ -555,6 +582,7 @@ fn can_be_fast_api(core: &TokenStream2, f: &syn::ItemFn) -> Option<FastApiSyn> {
args: args.parse().unwrap(), args: args.parse().unwrap(),
ret, ret,
use_recv, use_recv,
slices,
v8_values, v8_values,
use_fast_cb_opts, use_fast_cb_opts,
}) })
@ -692,6 +720,44 @@ fn codegen_arg(
}; };
}; };
} }
// Fast path for &/&mut [u8] and &/&mut [u32]
if let Some(ty) = is_ref_slice(&**ty) {
let (ptr_ty, mutability) = match ty {
SliceType::U8 => (quote!([u8]), quote!(&)),
SliceType::U8Mut => (quote!([u8]), quote!(&mut)),
};
return quote! {
let #ident = {
let value = args.get(#idx as i32);
if let Ok(view) = #core::v8::Local::<#core::v8::ArrayBufferView>::try_from(value) {
let (offset, len) = (view.byte_offset(), view.byte_length());
let buffer = match view.buffer(scope) {
Some(v) => v,
None => {
return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx));
}
};
let store = buffer.get_backing_store();
if store.is_shared() {
return #core::_ops::throw_type_error(scope, format!("Expected non-shared ArrayBufferView at position {}", #idx));
}
unsafe { #mutability *(&store[offset..offset + len] as *const _ as *mut #ptr_ty) }
} else {
let b: #core::v8::Local<#core::v8::ArrayBuffer> = match value.try_into() {
Ok(v) => v,
Err(_) => {
return #core::_ops::throw_type_error(scope, format!("Expected ArrayBuffer at position {}", #idx));
}
};
let store = b.get_backing_store();
if store.is_shared() {
return #core::_ops::throw_type_error(scope, format!("Expected non-shared ArrayBufferView at position {}", #idx));
}
unsafe { #mutability *(&store[0..b.byte_length()] as *const _ as *mut #ptr_ty) }
}
};
};
}
// Otherwise deserialize it via serde_v8 // Otherwise deserialize it via serde_v8
quote! { quote! {
let #ident = args.get(#idx as i32); let #ident = args.get(#idx as i32);
@ -780,6 +846,29 @@ fn is_option_string(ty: impl ToTokens) -> bool {
tokens(ty) == "Option < String >" tokens(ty) == "Option < String >"
} }
enum SliceType {
U8,
U8Mut,
}
fn is_ref_slice(ty: impl ToTokens) -> Option<SliceType> {
if is_u8_slice(&ty) {
return Some(SliceType::U8);
}
if is_u8_slice_mut(&ty) {
return Some(SliceType::U8Mut);
}
None
}
fn is_u8_slice(ty: impl ToTokens) -> bool {
tokens(ty) == "& [u8]"
}
fn is_u8_slice_mut(ty: impl ToTokens) -> bool {
tokens(ty) == "& mut [u8]"
}
fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool { fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool {
tokens(&ty).contains("Option < & mut FastApiCallbackOptions") tokens(&ty).contains("Option < & mut FastApiCallbackOptions")
} }