From f68d5e57fce87f5c19e0f05bee116a3d7b2e0085 Mon Sep 17 00:00:00 2001 From: Keyhan Vakil Date: Thu, 22 Jun 2023 17:07:18 +0000 Subject: [PATCH 1/2] deps: V8: cherry-pick 1a782f6543ae MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [base] add build flag to use MADV_DONTFORK Embedders like Node.js and Electron expose fork(2)/execve(2) to their users. Unfortunately when the V8 heap is very large, these APIs become rather slow on Linux, due to the kernel needing to do all the bookkeeping for the forked process (in clone's dup_mmap and execve's exec_mmap). Of course, this is useless because the forked child thread will never actually need to access the V8 heap. Add a new build flag v8_enable_private_mapping_fork_optimization which marks all pages allocated by OS::Allocate as MADV_DONTFORK. This improves the performance of Node.js's fork/execve combination by 10x on a 600 MB heap. Fixed: v8:7381 Change-Id: Ib649f774d4a932b41886313ce89acc369923699d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4602858 Commit-Queue: Michael Lippautz Reviewed-by: Michael Lippautz Cr-Commit-Position: refs/heads/main@{#88447} Refs: https://github.com/v8/v8/commit/1a782f6543ae999bf9e497e23042af0b70191f10 PR-URL: https://github.com/nodejs/node/pull/48523 Fixes: https://github.com/nodejs/node/issues/25382 Fixes: https://github.com/nodejs/node/issues/14917 Refs: https://github.com/nodejs/performance/issues/93 Refs: https://github.com/nodejs/performance/issues/89 Reviewed-By: Yagiz Nizipli Reviewed-By: Juan José Arboleda Reviewed-By: Debadree Chatterjee --- common.gypi | 2 +- deps/v8/BUILD.gn | 14 ++++++++++++++ deps/v8/src/base/platform/platform-posix.cc | 6 ++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index 94fae065189043..36bfa41701aef3 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.26', + 'v8_embedder_string': '-node.27', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/BUILD.gn b/deps/v8/BUILD.gn index 988c907d964a25..f6860561cad2bd 100644 --- a/deps/v8/BUILD.gn +++ b/deps/v8/BUILD.gn @@ -82,6 +82,17 @@ declare_args() { # Sets -dENABLE_HUGEPAGE v8_enable_hugepage = false + # Sets -dV8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION. + # + # This flag speeds up the performance of fork/execve on Linux systems for + # embedders which use it (like Node.js). It works by marking the pages that + # V8 allocates as MADV_DONTFORK. Without MADV_DONTFORK, the Linux kernel + # spends a long time manipulating page mappings on fork and exec which the + # child process doesn't generally need to access. + # + # See v8:7381 for more details. + v8_enable_private_mapping_fork_optimization = false + # Sets -dENABLE_HANDLE_ZAPPING. v8_enable_handle_zapping = !is_on_release_branch || is_debug @@ -862,6 +873,9 @@ config("features") { if (v8_enable_hugepage) { defines += [ "ENABLE_HUGEPAGE" ] } + if (v8_enable_private_mapping_fork_optimization) { + defines += [ "V8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION" ] + } if (v8_enable_object_print) { defines += [ "OBJECT_PRINT" ] } diff --git a/deps/v8/src/base/platform/platform-posix.cc b/deps/v8/src/base/platform/platform-posix.cc index 664ed301c87baa..517cbfccab6843 100644 --- a/deps/v8/src/base/platform/platform-posix.cc +++ b/deps/v8/src/base/platform/platform-posix.cc @@ -174,6 +174,12 @@ void* Allocate(void* hint, size_t size, OS::MemoryPermission access, int flags = GetFlagsForMemoryPermission(access, page_type); void* result = mmap(hint, size, prot, flags, kMmapFd, kMmapFdOffset); if (result == MAP_FAILED) return nullptr; + +#if V8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION + // This is advisory, so we ignore errors. + madvise(result, size, MADV_DONTFORK); +#endif + #if ENABLE_HUGEPAGE if (result != nullptr && size >= kHugePageSize) { const uintptr_t huge_start = From b9c294b919fb2b224f319704ae6d9a006e313a96 Mon Sep 17 00:00:00 2001 From: Keyhan Vakil Date: Thu, 22 Jun 2023 17:08:58 +0000 Subject: [PATCH 2/2] child_process: improve spawn performance on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Speed up child_process.spawn by enabling the new V8 build flag which makes fork/exec faster. Here are the results of running the existing benchmark. Note that this optimization helps more for applications with larger heaps, so this is somewhat of an underestimate of the real world performance benefits. ```console $ ./node benchmark/compare.js --runs 15 \ --new ./node \ --old ~/node-v20/out/Release/node \ --filter params child_process > cpr $ node-benchmark-compare cpr confidence improvement (***) methodName='exec' n=1000 *** 60.84 % ±5.43% methodName='execFile' n=1000 *** 53.72 % ±3.33% methodName='execFileSync' n=1000 *** 9.10 % ±0.84% methodName='execSync' n=1000 *** 10.44 % ±0.97% methodName='spawn' n=1000 *** 53.10 % ±2.90% methodName='spawnSync' n=1000 *** 8.64 % ±1.22% 0.01 false positives, when considering a 0.1% risk acceptance (***) ``` Fixes: https://github.com/nodejs/node/issues/25382 Fixes: https://github.com/nodejs/node/issues/14917 Refs: https://github.com/nodejs/performance/issues/93 Refs: https://github.com/nodejs/performance/issues/89 PR-URL: https://github.com/nodejs/node/pull/48523 Refs: https://github.com/v8/v8/commit/1a782f6543ae999bf9e497e23042af0b70191f10 Reviewed-By: Yagiz Nizipli Reviewed-By: Juan José Arboleda Reviewed-By: Debadree Chatterjee --- tools/v8_gypfiles/features.gypi | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/v8_gypfiles/features.gypi b/tools/v8_gypfiles/features.gypi index 82c95b4d6db5ee..3c94b0bca4c383 100644 --- a/tools/v8_gypfiles/features.gypi +++ b/tools/v8_gypfiles/features.gypi @@ -68,6 +68,20 @@ }, { 'v8_enable_system_instrumentation': 0, }], + ['OS=="linux"', { + # Sets -dV8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION. + # + # This flag speeds up the performance of fork/execve on Linux systems for + # embedders which use it (like Node.js). It works by marking the pages that + # V8 allocates as MADV_DONTFORK. Without MADV_DONTFORK, the Linux kernel + # spends a long time manipulating page mappings on fork and exec which the + # child process doesn't generally need to access. + # + # See v8:7381 for more details. + 'v8_enable_private_mapping_fork_optimization': 1, + }, { + 'v8_enable_private_mapping_fork_optimization': 0, + }], ], 'is_debug%': 0, @@ -310,6 +324,9 @@ ['v8_enable_hugepage==1', { 'defines': ['ENABLE_HUGEPAGE',], }], + ['v8_enable_private_mapping_fork_optimization==1', { + 'defines': ['V8_ENABLE_PRIVATE_MAPPING_FORK_OPTIMIZATION'], + }], ['v8_enable_vtunejit==1', { 'defines': ['ENABLE_VTUNE_JIT_INTERFACE',], }],