Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v10.x] Backport large pages to v10.x #31105

Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
src: make --use-largepages a runtime option
Moves the option that instructs Node.js to-remap its static code to
large pages from a configure-time option to a runtime option. This
should make it easy to assess the performance impact of such a change
without having to custom-build.

PR-URL: #30954
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: David Carlier <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Gireesh Punathil <[email protected]>
Reviewed-By: Denys Otrishko <[email protected]>
Co-authored-by: David Carlier <[email protected]>
  • Loading branch information
Gabriel Schulhof and devnexen committed Mar 5, 2020
commit b4d01ba466e9118481144048e2fc09cab2111323
33 changes: 0 additions & 33 deletions configure.py
Original file line number Diff line number Diff line change
@@ -392,17 +392,6 @@
dest='with_etw',
help='build with ETW (default is true on Windows)')

parser.add_option('--use-largepages',
action='store_true',
dest='node_use_large_pages',
help='build with Large Pages support. This feature is supported only on Linux kernel' +
'>= 2.6.38 with Transparent Huge pages enabled')

parser.add_option('--use-largepages-script-lld',
action='store_true',
dest='node_use_large_pages_script_lld',
help='link against the LLVM ld linker script. Implies -fuse-ld=lld in the linker flags')

intl_optgroup.add_option('--with-intl',
action='store',
dest='with_intl',
@@ -1030,28 +1019,6 @@ def configure_node(o):
else:
o['variables']['node_use_dtrace'] = 'false'

if options.node_use_large_pages and not flavor in ('linux', 'freebsd', 'mac'):
raise Exception(
'Large pages are supported only on Linux, FreeBSD and MacOS Systems.')
if options.node_use_large_pages and flavor in ('linux', 'freebsd', 'mac'):
if options.shared or options.enable_static:
raise Exception(
'Large pages are supported only while creating node executable.')
if target_arch!="x64":
raise Exception(
'Large pages are supported only x64 platform.')
if flavor == 'mac':
info('macOS server with 32GB or more is recommended')
if flavor == 'linux':
# Example full version string: 2.6.32-696.28.1.el6.x86_64
FULL_KERNEL_VERSION=os.uname()[2]
KERNEL_VERSION=FULL_KERNEL_VERSION.split('-')[0]
if KERNEL_VERSION < "2.6.38" and flavor == 'linux':
raise Exception(
'Large pages need Linux kernel version >= 2.6.38')
o['variables']['node_use_large_pages'] = b(options.node_use_large_pages)
o['variables']['node_use_large_pages_script_lld'] = b(options.node_use_large_pages_script_lld)

if options.no_ifaddrs:
o['defines'] += ['SUNOS_NO_IFADDRS']

17 changes: 17 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
@@ -480,6 +480,22 @@ environment variables.

See `SSL_CERT_DIR` and `SSL_CERT_FILE`.

### `--use-largepages=mode`
<!-- YAML
added: REPLACEME
-->

Re-map the Node.js static code to large memory pages at startup. If supported on
the target system, this will cause the Node.js static code to be moved onto 2
MiB pages instead of 4 KiB pages.

The following values are valid for `mode`:
* `off`: No mapping will be attempted. This is the default.
* `on`: If supported by the OS, mapping will be attempted. Failure to map will
be ignored and a message will be printed to standard error.
* `silent`: If supported by the OS, mapping will be attempted. Failure to map
will be ignored and will not be reported.

### `--v8-options`
<!-- YAML
added: v0.1.3
@@ -676,6 +692,7 @@ Node.js options that are allowed are:
- `--track-heap-objects`
- `--unhandled-rejections`
- `--use-bundled-ca`
- `--use-largepages`
- `--use-openssl-ca`
- `--v8-pool-size`
- `--zero-fill-buffers`
10 changes: 10 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
@@ -259,6 +259,16 @@ See
and
.Ev SSL_CERT_FILE .
.
.It Fl -use-largepages Ns = Ns Ar mode
Re-map the Node.js static code to large memory pages at startup. If supported on
the target system, this will cause the Node.js static code to be moved onto 2
MiB pages instead of 4 KiB pages.
.Pp
.Ar mode
must have one of the following values:
`off` (the default value, meaning do not map), `on` (map and ignore failure,
reporting it to stderr), or `silent` (map and silently ignore failure).
.
.It Fl -v8-options
Print V8 command-line options.
.
5 changes: 2 additions & 3 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -606,10 +606,9 @@
'src/tls_wrap.h'
],
}],
[ 'node_use_large_pages=="true" and OS in "linux freebsd mac"', {
[ 'OS in "linux freebsd mac" and '
'target_arch=="x64"', {
'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ],
# The current implementation of Large Pages is under Linux.
# Other implementations are possible but not currently supported.
'sources': [
'src/large_pages/node_large_page.cc',
'src/large_pages/node_large_page.h'
6 changes: 2 additions & 4 deletions node.gypi
Original file line number Diff line number Diff line change
@@ -306,17 +306,15 @@
}],
[ 'OS=="linux" and '
'target_arch=="x64" and '
'node_use_large_pages=="true" and '
'node_use_large_pages_script_lld=="false"', {
'llvm_version==0', {
'ldflags': [
'-Wl,-T',
'<!(realpath src/large_pages/ld.implicit.script)',
]
}],
[ 'OS=="linux" and '
'target_arch=="x64" and '
'node_use_large_pages=="true" and '
'node_use_large_pages_script_lld=="true"', {
'llvm_version!=0', {
'ldflags': [
'-Wl,-T',
'<!(realpath src/large_pages/ld.implicit.script.lld)',
20 changes: 12 additions & 8 deletions src/large_pages/node_large_page.cc
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
//
// SPDX-License-Identifier: MIT

#include "node_internals.h"
#include "node_large_page.h"
#include "util.h"
#include "uv.h"
@@ -69,7 +70,7 @@
// Map a new area and copy the original code there
// Use mmap using the start address with MAP_FIXED so we get exactly the
// same virtual address
// Use madvise with MADV_HUGE_PAGE to use Anonymous 2M Pages
// Use madvise with MADV_HUGEPAGE to use Anonymous 2M Pages
// If successful copy the code there and unmap the original region.

extern char __nodetext;
@@ -315,7 +316,7 @@ static bool IsSuperPagesEnabled() {
// a. map a new area and copy the original code there
// b. mmap using the start address with MAP_FIXED so we get exactly
// the same virtual address (except on macOS).
// c. madvise with MADV_HUGE_PAGE
// c. madvise with MADV_HUGEPAGE
// d. If successful copy the code there and unmap the original region
int
#if !defined(__APPLE__)
@@ -340,9 +341,6 @@ MoveTextRegionToLargePages(const text_region& r) {
PrintSystemError(errno);
return -1;
}
OnScopeLeave munmap_on_return([nmem, size]() {
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
});

memcpy(nmem, r.from, size);

@@ -359,13 +357,14 @@ MoveTextRegionToLargePages(const text_region& r) {
return -1;
}

ret = madvise(tmem, size, MADV_HUGEPAGE);
ret = madvise(tmem, size, 14 /* MADV_HUGEPAGE */);
if (ret == -1) {
PrintSystemError(errno);
ret = munmap(tmem, size);
if (ret == -1) {
PrintSystemError(errno);
}
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
memcpy(start, nmem, size);
@@ -376,6 +375,7 @@ MoveTextRegionToLargePages(const text_region& r) {
MAP_ALIGNED_SUPER, -1 , 0);
if (tmem == MAP_FAILED) {
PrintSystemError(errno);
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
#elif defined(__APPLE__)
@@ -390,6 +390,7 @@ MoveTextRegionToLargePages(const text_region& r) {
VM_FLAGS_SUPERPAGE_SIZE_2MB, 0);
if (tmem == MAP_FAILED) {
PrintSystemError(errno);
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
memcpy(tmem, nmem, size);
@@ -400,6 +401,7 @@ MoveTextRegionToLargePages(const text_region& r) {
if (ret == -1) {
PrintSystemError(errno);
}
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
memcpy(start, tmem, size);
@@ -412,8 +414,10 @@ MoveTextRegionToLargePages(const text_region& r) {
if (ret == -1) {
PrintSystemError(errno);
}
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return ret;
}

@@ -425,12 +429,12 @@ int MapStaticCodeToLargePages() {
return -1;
}

#if defined(__linux__)
#if defined(__linux__) || defined(__FreeBSD__)
if (r.from > reinterpret_cast<void*>(&MoveTextRegionToLargePages))
return MoveTextRegionToLargePages(r);

return -1;
#elif defined(__FreeBSD__) || defined(__APPLE__)
#elif defined(__APPLE__)
return MoveTextRegionToLargePages(r);
#endif
}
30 changes: 20 additions & 10 deletions src/node.cc
Original file line number Diff line number Diff line change
@@ -74,9 +74,7 @@
#include "../deps/v8/src/third_party/vtune/v8-vtune.h"
#endif

#ifdef NODE_ENABLE_LARGE_CODE_PAGES
#include "large_pages/node_large_page.h"
#endif

#include <errno.h>
#include <fcntl.h> // _O_RDWR
@@ -3021,14 +3019,6 @@ int Start(int argc, char** argv) {

CHECK_GT(argc, 0);

#ifdef NODE_ENABLE_LARGE_CODE_PAGES
if (node::IsLargePagesEnabled()) {
if (node::MapStaticCodeToLargePages() != 0) {
fprintf(stderr, "Reverting to default page size\n");
}
}
#endif

// Hack around with the argv pointer. Used for process.title = "blah".
argv = uv_setup_args(argc, argv);

@@ -3037,6 +3027,26 @@ int Start(int argc, char** argv) {
// This needs to run *before* V8::Initialize().
Init(&args, &exec_args);

#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES
if (per_process_opts->use_largepages == "on" ||
per_process_opts->use_largepages == "silent") {
if (node::IsLargePagesEnabled()) {
if (node::MapStaticCodeToLargePages() != 0 &&
per_process_opts->use_largepages != "silent") {
fprintf(stderr,
"Mapping code to large pages failed. Reverting to default page "
"size.\n");
}
} else if (per_process_opts->use_largepages != "silent") {
fprintf(stderr, "Large pages are not enabled.\n");
}
}
#else
if (per_process_opts->use_largepages == "on") {
fprintf(stderr, "Mapping to large pages is not supported.\n");
}
#endif // NODE_ENABLE_LARGE_CODE_PAGES

#if HAVE_OPENSSL
{
std::string extra_ca_certs;
9 changes: 9 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
@@ -24,6 +24,11 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) {
"used, not both");
}
#endif
if (use_largepages != "off" &&
use_largepages != "on" &&
use_largepages != "silent") {
errors->push_back("invalid value for --use-largepages");
}
per_isolate->CheckOptions(errors);
}

@@ -355,6 +360,10 @@ PerProcessOptionsParser::PerProcessOptionsParser() {
kAllowedInEnvironment);
#endif
#endif
AddOption("--use-largepages",
"Map the Node.js static code to large pages",
&PerProcessOptions::use_largepages,
kAllowedInEnvironment);

Insert(&PerIsolateOptionsParser::instance,
&PerProcessOptions::get_per_isolate_options);
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
@@ -153,6 +153,7 @@ class PerProcessOptions : public Options {
bool force_fips_crypto = false;
#endif
#endif
std::string use_largepages = "off";

inline PerIsolateOptions* get_per_isolate_options();
void CheckOptions(std::vector<std::string>* errors);
29 changes: 29 additions & 0 deletions test/parallel/test-startup-large-pages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

// Make sure that Node.js runs correctly with the --use-largepages option.

require('../common');
const assert = require('assert');
const { spawnSync } = require('child_process');

{
const child = spawnSync(process.execPath,
[ '--use-largepages=on', '-p', '42' ]);
const stdout = child.stdout.toString().match(/\S+/g);
assert.strictEqual(child.status, 0);
assert.strictEqual(child.signal, null);
assert.strictEqual(stdout.length, 1);
assert.strictEqual(stdout[0], '42');
}

{
const child = spawnSync(process.execPath,
[ '--use-largepages=xyzzy', '-p', '42' ]);
assert.strictEqual(child.status, 9);
assert.strictEqual(child.signal, null);
assert.strictEqual(child.stderr.toString().match(/\S+/g).slice(1).join(' '),
'invalid value for --use-largepages');
}

// TODO(gabrielschulhof): Make assertions about the stderr, which may or may not
// contain a message indicating that mapping to large pages has failed.