src,tools: speed up startup by 2.5%

Use zero-copy external string resources for storing the built-in JS
source code.  Saves a few hundred kilobyte of memory and consistently
speeds up `benchmark/misc/startup.js` by 2.5%.

Everything old is new again!  Commit 74954ce ("Add string class that
uses ExternalAsciiStringResource.") from 2011 did the same thing but
I removed that in 2013 in commit 34b0a36 ("src: don't use NewExternal()
with unaligned strings") because of a limitation in the V8 API.

V8 no longer requires that strings are aligned if they are one-byte
strings so it should be safe to re-enable external strings again.

PR-URL: https://github.com/nodejs/node/pull/5458
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Jefe Lindstaedt <robert.lindstaedt@gmail.com>
This commit is contained in:
Ben Noordhuis 2016-05-13 23:08:43 +02:00
parent 2a456162b6
commit e4290dec2d
2 changed files with 61 additions and 150 deletions

View File

@ -6,33 +6,46 @@
namespace node { namespace node {
using v8::HandleScope;
using v8::Local; using v8::Local;
using v8::NewStringType; using v8::NewStringType;
using v8::Object; using v8::Object;
using v8::String; using v8::String;
// id##_data is defined in node_natives.h.
#define V(id) \
static struct : public String::ExternalOneByteStringResource { \
const char* data() const override { \
return reinterpret_cast<const char*>(id##_data); \
} \
size_t length() const override { return sizeof(id##_data); } \
void Dispose() override { /* Default calls `delete this`. */ } \
} id##_external_data;
NODE_NATIVES_MAP(V)
#undef V
Local<String> MainSource(Environment* env) { Local<String> MainSource(Environment* env) {
return String::NewFromUtf8( auto maybe_string =
env->isolate(), String::NewExternalOneByte(
reinterpret_cast<const char*>(internal_bootstrap_node_native), env->isolate(),
NewStringType::kNormal, &internal_bootstrap_node_external_data);
sizeof(internal_bootstrap_node_native)).ToLocalChecked(); return maybe_string.ToLocalChecked();
} }
void DefineJavaScript(Environment* env, Local<Object> target) { void DefineJavaScript(Environment* env, Local<Object> target) {
HandleScope scope(env->isolate()); auto context = env->context();
#define V(id) \
for (auto native : natives) { do { \
if (native.source != internal_bootstrap_node_native) { auto key = \
Local<String> name = String::NewFromUtf8(env->isolate(), native.name); String::NewFromOneByte( \
Local<String> source = env->isolate(), id##_name, NewStringType::kNormal, \
String::NewFromUtf8( sizeof(id##_name)).ToLocalChecked(); \
env->isolate(), reinterpret_cast<const char*>(native.source), auto value = \
NewStringType::kNormal, native.source_len).ToLocalChecked(); String::NewExternalOneByte( \
target->Set(name, source); env->isolate(), &id##_external_data).ToLocalChecked(); \
} CHECK(target->Set(context, key, value).FromJust()); \
} } while (0);
NODE_NATIVES_MAP(V)
#undef V
} }
} // namespace node } // namespace node

View File

@ -37,8 +37,11 @@ import sys
import string import string
def ToCArray(filename, lines): def ToCString(contents):
return ','.join(str(ord(c)) for c in lines) step = 20
slices = (contents[i:i+step] for i in xrange(0, len(contents), step))
slices = map(lambda s: ','.join(str(ord(c)) for c in s), slices)
return ',\n'.join(slices)
def ReadFile(filename): def ReadFile(filename):
@ -61,21 +64,6 @@ def ReadLines(filename):
return result return result
def LoadConfigFrom(name):
import ConfigParser
config = ConfigParser.ConfigParser()
config.read(name)
return config
def ParseValue(string):
string = string.strip()
if string.startswith('[') and string.endswith(']'):
return string.lstrip('[').rstrip(']').split()
else:
return string
def ExpandConstants(lines, constants): def ExpandConstants(lines, constants):
for key, value in constants.items(): for key, value in constants.items():
lines = lines.replace(key, str(value)) lines = lines.replace(key, str(value))
@ -174,53 +162,37 @@ def ReadMacros(lines):
HEADER_TEMPLATE = """\ HEADER_TEMPLATE = """\
#ifndef node_natives_h #ifndef NODE_NATIVES_H_
#define node_natives_h #define NODE_NATIVES_H_
namespace node {
%(source_lines)s\ #include <stdint.h>
struct _native { #define NODE_NATIVES_MAP(V) \\
const char* name; {node_natives_map}
const unsigned char* source;
size_t source_len;
};
static const struct _native natives[] = { %(native_lines)s }; namespace node {{
{sources}
}} // namespace node
} #endif // NODE_NATIVES_H_
#endif
""" """
NATIVE_DECLARATION = """\ NODE_NATIVES_MAP = """\
{ "%(id)s", %(escaped_id)s_native, sizeof(%(escaped_id)s_native) }, V({escaped_id}) \\
"""
SOURCE_DECLARATION = """\
const unsigned char %(escaped_id)s_native[] = { %(data)s };
""" """
GET_DELAY_INDEX_CASE = """\ SOURCES = """\
if (strcmp(name, "%(id)s") == 0) return %(i)i; static const uint8_t {escaped_id}_name[] = {{
{name}}};
static const uint8_t {escaped_id}_data[] = {{
{data}}};
""" """
GET_DELAY_SCRIPT_SOURCE_CASE = """\
if (index == %(i)i) return Vector<const char>(%(id)s, %(length)i);
"""
GET_DELAY_SCRIPT_NAME_CASE = """\
if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i);
"""
def JS2C(source, target): def JS2C(source, target):
ids = []
delay_ids = []
modules = [] modules = []
# Locate the macros file name.
consts = {} consts = {}
macros = {} macros = {}
macro_lines = [] macro_lines = []
@ -235,18 +207,14 @@ def JS2C(source, target):
(consts, macros) = ReadMacros(macro_lines) (consts, macros) = ReadMacros(macro_lines)
# Build source code lines # Build source code lines
source_lines = [ ] node_natives_map = []
source_lines_empty = [] sources = []
native_lines = []
for s in modules: for s in modules:
delay = str(s).endswith('-delay.js')
lines = ReadFile(str(s)) lines = ReadFile(str(s))
lines = ExpandConstants(lines, consts) lines = ExpandConstants(lines, consts)
lines = ExpandMacros(lines, macros) lines = ExpandMacros(lines, macros)
data = ToCArray(s, lines) data = ToCString(lines)
# On Windows, "./foo.bar" in the .gyp file is passed as "foo.bar" # On Windows, "./foo.bar" in the .gyp file is passed as "foo.bar"
# so don't assume there is always a slash in the file path. # so don't assume there is always a slash in the file path.
@ -258,89 +226,19 @@ def JS2C(source, target):
if '.' in id: if '.' in id:
id = id.split('.', 1)[0] id = id.split('.', 1)[0]
if delay: id = id[:-6] name = ToCString(id)
if delay:
delay_ids.append((id, len(lines)))
else:
ids.append((id, len(lines)))
escaped_id = id.replace('-', '_').replace('/', '_') escaped_id = id.replace('-', '_').replace('/', '_')
source_lines.append(SOURCE_DECLARATION % { node_natives_map.append(NODE_NATIVES_MAP.format(**locals()))
'id': id, sources.append(SOURCES.format(**locals()))
'escaped_id': escaped_id,
'data': data
})
source_lines_empty.append(SOURCE_DECLARATION % {
'id': id,
'escaped_id': escaped_id,
'data': 0
})
native_lines.append(NATIVE_DECLARATION % {
'id': id,
'escaped_id': escaped_id
})
# Build delay support functions node_natives_map = ''.join(node_natives_map)
get_index_cases = [ ] sources = ''.join(sources)
get_script_source_cases = [ ]
get_script_name_cases = [ ]
i = 0
for (id, length) in delay_ids:
native_name = "native %s.js" % id
get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i })
get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % {
'id': id,
'length': length,
'i': i
})
get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % {
'name': native_name,
'length': len(native_name),
'i': i
});
i = i + 1
for (id, length) in ids:
native_name = "native %s.js" % id
get_index_cases.append(GET_DELAY_INDEX_CASE % { 'id': id, 'i': i })
get_script_source_cases.append(GET_DELAY_SCRIPT_SOURCE_CASE % {
'id': id,
'length': length,
'i': i
})
get_script_name_cases.append(GET_DELAY_SCRIPT_NAME_CASE % {
'name': native_name,
'length': len(native_name),
'i': i
});
i = i + 1
# Emit result # Emit result
output = open(str(target[0]), "w") output = open(str(target[0]), "w")
output.write(HEADER_TEMPLATE % { output.write(HEADER_TEMPLATE.format(**locals()))
'builtin_count': len(ids) + len(delay_ids),
'delay_count': len(delay_ids),
'source_lines': "\n".join(source_lines),
'native_lines': "\n".join(native_lines),
'get_index_cases': "".join(get_index_cases),
'get_script_source_cases': "".join(get_script_source_cases),
'get_script_name_cases': "".join(get_script_name_cases)
})
output.close() output.close()
if len(target) > 1:
output = open(str(target[1]), "w")
output.write(HEADER_TEMPLATE % {
'builtin_count': len(ids) + len(delay_ids),
'delay_count': len(delay_ids),
'source_lines': "\n".join(source_lines_empty),
'get_index_cases': "".join(get_index_cases),
'get_script_source_cases': "".join(get_script_source_cases),
'get_script_name_cases': "".join(get_script_name_cases)
})
output.close()
def main(): def main():
natives = sys.argv[1] natives = sys.argv[1]
source_files = sys.argv[2:] source_files = sys.argv[2:]