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

[v12.x] src: make --use-largepages a runtime option #31063

Closed
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 8 additions & 24 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,13 +401,12 @@
parser.add_option('--use-largepages',
richardlau marked this conversation as resolved.
Show resolved Hide resolved
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 and FreeBSD')
help='This option has no effect. --use-largepages is now a runtime option.')

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')
help='This option has no effect. --use-largepages is now a runtime option.')

intl_optgroup.add_option('--with-intl',
action='store',
Expand Down Expand Up @@ -1041,27 +1040,12 @@ 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.node_use_large_pages or options.node_use_large_pages_script_lld:
warn('''The `--use-largepages` and `--use-largepages-script-lld` options
have no effect during build time. Support for mapping to large pages is
now a runtime option of Node.js. Run `node --use-largepages` or add
`--use-largepages` to the `NODE_OPTIONS` environment variable once
Node.js is built to enable mapping to large pages.''')

if options.no_ifaddrs:
o['defines'] += ['SUNOS_NO_IFADDRS']
Expand Down
17 changes: 17 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,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
Expand Down Expand Up @@ -1085,6 +1101,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`
Expand Down
10 changes: 10 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,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.
.
Expand Down
5 changes: 2 additions & 3 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -824,10 +824,9 @@
}],
],
}],
[ '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'
Expand Down
10 changes: 4 additions & 6 deletions node.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -302,20 +302,18 @@
}],
[ 'OS=="linux" and '
'target_arch=="x64" and '
'node_use_large_pages=="true" and '
'node_use_large_pages_script_lld=="false"', {
'llvm_version=="0.0"', {
'ldflags': [
'-Wl,-T',
'<!(realpath src/large_pages/ld.implicit.script)',
'<!(echo "$(pwd)/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.0"', {
'ldflags': [
'-Wl,-T',
'<!(realpath src/large_pages/ld.implicit.script.lld)',
'<!(echo "$(pwd)/src/large_pages/ld.implicit.script.lld")',
]
}],
[ 'node_use_openssl=="true"', {
Expand Down
19 changes: 11 additions & 8 deletions src/large_pages/node_large_page.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,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;
Expand Down Expand Up @@ -308,7 +308,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__)
Expand All @@ -333,9 +333,6 @@ MoveTextRegionToLargePages(const text_region& r) {
PrintSystemError(errno);
return -1;
}
OnScopeLeave munmap_on_return([nmem, size]() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only conflict. It was expecting to remove auto munmap_on_return = OnScopeLeave(...); and not OnScopeLeave munmap_on_return(...);

if (-1 == munmap(nmem, size)) PrintSystemError(errno);
});

memcpy(nmem, r.from, size);

Expand All @@ -352,13 +349,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);
Expand All @@ -369,6 +367,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__)
Expand All @@ -383,6 +382,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);
Expand All @@ -393,6 +393,7 @@ MoveTextRegionToLargePages(const text_region& r) {
if (ret == -1) {
PrintSystemError(errno);
}
if (-1 == munmap(nmem, size)) PrintSystemError(errno);
return -1;
}
memcpy(start, tmem, size);
Expand All @@ -405,8 +406,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;
}

Expand All @@ -418,12 +421,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
}
Expand Down
28 changes: 20 additions & 8 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -887,14 +887,6 @@ InitializationResult InitializeOncePerProcess(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);

Expand All @@ -914,6 +906,26 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) {
}
}

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

if (per_process::cli_options->print_version) {
printf("%s\n", NODE_VERSION);
result.exit_code = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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);
}

Expand Down Expand Up @@ -745,6 +750,10 @@ PerProcessOptionsParser::PerProcessOptionsParser(
kAllowedInEnvironment);
#endif
#endif
AddOption("--use-largepages",
"Map the Node.js static code to large pages",
&PerProcessOptions::use_largepages,
kAllowedInEnvironment);

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

#ifdef NODE_REPORT
std::vector<std::string> cmdline;
Expand Down
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.