From 85a9295813530ea274013cb64bb8f79a0ac8c119 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 13 May 2016 23:08:43 +0200 Subject: [PATCH] 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 Reviewed-By: Robert Jefe Lindstaedt --- src/node_javascript.cc | 49 +++++++----- tools/js2c.py | 164 ++++++++--------------------------------- 2 files changed, 62 insertions(+), 151 deletions(-) diff --git a/src/node_javascript.cc b/src/node_javascript.cc index 6f445f76df2407..3f6d6c82a85269 100644 --- a/src/node_javascript.cc +++ b/src/node_javascript.cc @@ -6,33 +6,46 @@ namespace node { -using v8::HandleScope; using v8::Local; using v8::NewStringType; using v8::Object; 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(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 MainSource(Environment* env) { - return String::NewFromUtf8( - env->isolate(), - reinterpret_cast(internal_bootstrap_node_native), - NewStringType::kNormal, - sizeof(internal_bootstrap_node_native)).ToLocalChecked(); + auto maybe_string = + String::NewExternalOneByte( + env->isolate(), + &internal_bootstrap_node_external_data); + return maybe_string.ToLocalChecked(); } void DefineJavaScript(Environment* env, Local target) { - HandleScope scope(env->isolate()); - - for (auto native : natives) { - if (native.source != internal_bootstrap_node_native) { - Local name = String::NewFromUtf8(env->isolate(), native.name); - Local source = - String::NewFromUtf8( - env->isolate(), reinterpret_cast(native.source), - NewStringType::kNormal, native.source_len).ToLocalChecked(); - target->Set(name, source); - } - } + auto context = env->context(); +#define V(id) \ + do { \ + auto key = \ + String::NewFromOneByte( \ + env->isolate(), id##_name, NewStringType::kNormal, \ + sizeof(id##_name)).ToLocalChecked(); \ + auto value = \ + String::NewExternalOneByte( \ + env->isolate(), &id##_external_data).ToLocalChecked(); \ + CHECK(target->Set(context, key, value).FromJust()); \ + } while (0); + NODE_NATIVES_MAP(V) +#undef V } } // namespace node diff --git a/tools/js2c.py b/tools/js2c.py index 40c30cc0325f61..4808c56813ce17 100755 --- a/tools/js2c.py +++ b/tools/js2c.py @@ -37,8 +37,11 @@ import string -def ToCArray(filename, lines): - return ','.join(str(ord(c)) for c in lines) +def ToCString(contents): + 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): @@ -61,21 +64,6 @@ def ReadLines(filename): 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): for key, value in constants.items(): lines = lines.replace(key, str(value)) @@ -174,53 +162,37 @@ def ReadMacros(lines): HEADER_TEMPLATE = """\ -#ifndef node_natives_h -#define node_natives_h -namespace node { - -%(source_lines)s\ +#ifndef NODE_NATIVES_H_ +#define NODE_NATIVES_H_ -struct _native { - const char* name; - const unsigned char* source; - size_t source_len; -}; +#include -static const struct _native natives[] = { %(native_lines)s }; +#define NODE_NATIVES_MAP(V) \\ +{node_natives_map} -} -#endif -""" - - -NATIVE_DECLARATION = """\ - { "%(id)s", %(escaped_id)s_native, sizeof(%(escaped_id)s_native) }, -""" +namespace node {{ +{sources} +}} // namespace node -SOURCE_DECLARATION = """\ - const unsigned char %(escaped_id)s_native[] = { %(data)s }; +#endif // NODE_NATIVES_H_ """ -GET_DELAY_INDEX_CASE = """\ - if (strcmp(name, "%(id)s") == 0) return %(i)i; +NODE_NATIVES_MAP = """\ + V({escaped_id}) \\ """ -GET_DELAY_SCRIPT_SOURCE_CASE = """\ - if (index == %(i)i) return Vector(%(id)s, %(length)i); +SOURCES = """\ +static const uint8_t {escaped_id}_name[] = {{ +{name}}}; +static const uint8_t {escaped_id}_data[] = {{ +{data}}}; """ -GET_DELAY_SCRIPT_NAME_CASE = """\ - if (index == %(i)i) return Vector("%(name)s", %(length)i); -""" - def JS2C(source, target): - ids = [] - delay_ids = [] modules = [] - # Locate the macros file name. consts = {} macros = {} macro_lines = [] @@ -235,18 +207,14 @@ def JS2C(source, target): (consts, macros) = ReadMacros(macro_lines) # Build source code lines - source_lines = [ ] - source_lines_empty = [] - - native_lines = [] + node_natives_map = [] + sources = [] for s in modules: - delay = str(s).endswith('-delay.js') lines = ReadFile(str(s)) - lines = ExpandConstants(lines, consts) lines = ExpandMacros(lines, macros) - data = ToCArray(s, lines) + data = ToCString(lines) # 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. @@ -258,89 +226,19 @@ def JS2C(source, target): if '.' in id: id = id.split('.', 1)[0] - if delay: id = id[:-6] - if delay: - delay_ids.append((id, len(lines))) - else: - ids.append((id, len(lines))) - + name = ToCString(id) escaped_id = id.replace('-', '_').replace('/', '_') - source_lines.append(SOURCE_DECLARATION % { - 'id': id, - '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 - get_index_cases = [ ] - 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 + node_natives_map.append(NODE_NATIVES_MAP.format(**locals())) + sources.append(SOURCES.format(**locals())) + + node_natives_map = ''.join(node_natives_map) + sources = ''.join(sources) # Emit result output = open(str(target[0]), "w") - output.write(HEADER_TEMPLATE % { - '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.write(HEADER_TEMPLATE.format(**locals())) 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(): natives = sys.argv[1] source_files = sys.argv[2:]