From f92406630fedaf869340afac46706bf89521e8b7 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 2 May 2023 20:17:11 +0530 Subject: [PATCH] perf(core): use jemalloc for V8 array buffer allocator (#18875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commits changes "deno_core" to use jemalloc allocator as an allocator for V8 array buffers. This greatly improves our GC characteristics as we are using a lot of short lived array buffers. They no longer go through the expensive malloc/free cycle using the default Rust allocator, but instead use jemallocator's memory pool. As a result the flamegraphs for WS/HTTP server flamegraphs no longer show stacks for malloc/free around ops that use ZeroCopyBuf and &[u8]. --------- Co-authored-by: Bartek IwaƄczuk --- Cargo.lock | 11 +++++++++ core/Cargo.toml | 3 +++ core/runtime.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 77b6c0cbb7..f4647350b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,6 +875,7 @@ dependencies = [ "serde_v8", "smallvec", "sourcemap", + "tikv-jemalloc-sys", "tokio", "url", "v8", @@ -5130,6 +5131,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.3+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "time" version = "0.3.20" diff --git a/core/Cargo.toml b/core/Cargo.toml index 141583710b..0e0b1d2c7b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -39,6 +39,9 @@ sourcemap = "6.1" url.workspace = true v8.workspace = true +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemalloc-sys = "0.5" + [[example]] name = "http_bench_json_ops" path = "examples/http_bench_json_ops/main.rs" diff --git a/core/runtime.rs b/core/runtime.rs index 46256b8d8e..5470a89b9b 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -72,6 +72,48 @@ struct IsolateAllocations { Option<(Box>, v8::NearHeapLimitCallback)>, } +/// A custom allocator for array buffers for V8. It uses `jemalloc` so it's +/// not available on Windows. +#[cfg(not(target_env = "msvc"))] +mod custom_allocator { + use std::ffi::c_void; + + pub struct RustAllocator; + + pub unsafe extern "C" fn allocate( + _alloc: &RustAllocator, + n: usize, + ) -> *mut c_void { + tikv_jemalloc_sys::calloc(1, n) + } + + pub unsafe extern "C" fn allocate_uninitialized( + _alloc: &RustAllocator, + n: usize, + ) -> *mut c_void { + tikv_jemalloc_sys::malloc(n) + } + + pub unsafe extern "C" fn free( + _alloc: &RustAllocator, + data: *mut c_void, + _n: usize, + ) { + tikv_jemalloc_sys::free(data) + } + + pub unsafe extern "C" fn reallocate( + _alloc: &RustAllocator, + prev: *mut c_void, + _oldlen: usize, + newlen: usize, + ) -> *mut c_void { + tikv_jemalloc_sys::realloc(prev, newlen) + } + + pub unsafe extern "C" fn drop(_alloc: *const RustAllocator) {} +} + /// A single execution context of JavaScript. Corresponds roughly to the "Web /// Worker" concept in the DOM. A JsRuntime is a Future that can be used with /// an event loop (Tokio, async_std). @@ -393,6 +435,20 @@ impl JsRuntime { } (isolate, snapshot_options) } else { + #[cfg(not(target_env = "msvc"))] + let vtable: &'static v8::RustAllocatorVtable< + custom_allocator::RustAllocator, + > = &v8::RustAllocatorVtable { + allocate: custom_allocator::allocate, + allocate_uninitialized: custom_allocator::allocate_uninitialized, + free: custom_allocator::free, + reallocate: custom_allocator::reallocate, + drop: custom_allocator::drop, + }; + #[cfg(not(target_env = "msvc"))] + let allocator = Arc::new(custom_allocator::RustAllocator); + + #[allow(unused_mut)] let mut params = options .create_params .take() @@ -404,6 +460,14 @@ impl JsRuntime { }) .external_references(&**refs); + #[cfg(not(target_env = "msvc"))] + // SAFETY: We are leaking the created `allocator` variable so we're sure + // it will outlive the created isolate. We also made sure that the vtable + // is correct. + let mut params = params.array_buffer_allocator(unsafe { + v8::new_rust_allocator(Arc::into_raw(allocator), vtable) + }); + if let Some(snapshot) = options.startup_snapshot { params = match snapshot { Snapshot::Static(data) => params.snapshot_blob(data),