Skip to content

Commit

Permalink
src: make --use-largepages a runtime option
Browse files Browse the repository at this point in the history
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: nodejs#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
1 parent d5b5b22 commit b4d01ba
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 58 deletions.
33 changes: 0 additions & 33 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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']

Expand Down
17 changes: 17 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
10 changes: 10 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
.
Expand Down
5 changes: 2 additions & 3 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
6 changes: 2 additions & 4 deletions node.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
20 changes: 12 additions & 8 deletions src/large_pages/node_large_page.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
//
// SPDX-License-Identifier: MIT

#include "node_internals.h"
#include "node_large_page.h"
#include "util.h"
#include "uv.h"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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__)
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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__)
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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
}
Expand Down
30 changes: 20 additions & 10 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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.

0 comments on commit b4d01ba

Please sign in to comment.