Skip to content

Commit 060c1d5

Browse files
kvakiltargos
authored andcommitted
src: stop copying code cache, part 2
This removes more copies of the code cache data. First: for the builtin snapshot, we were copying the code cache to create a `std::vector<uint8_t>`. This was slowing down static intialization. Change it to use a good old `uint8_t*` and `size_t` rather than a vector. For the case of embedder provided snapshots, we also add an `owning_ptr` so that we can properly cleanup owned values created from the snapshot. Second: whenever the code cache was hit, we would remove the bytecode from the code cache, and then reserialize it from the compiled function. This was pretty slow. Change the code so that we can reuse the same code cache multiple times. If the code cache is rejected (say, because the user added V8 options), then we need to generate the bytecode, in which case we again use `owning_ptr` to ensure that the underlying code cache is freed. Combined, these changes improve the misc/startup.js benchmarks significantly (p < 0.001): * process,benchmark/fixtures/require-builtins: 22.15% * process,test/fixtures/semicolon: 8.55% * worker,benchmark/fixtures/require-builtins: 26.52% * worker,test/fixtures/semicolon: 21.52% PR-URL: #47958 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
1 parent b7f13a8 commit 060c1d5

File tree

3 files changed

+81
-51
lines changed

3 files changed

+81
-51
lines changed

src/node_builtins.cc

+22-33
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,6 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
154154
return builtin_categories;
155155
}
156156

157-
const ScriptCompiler::CachedData* BuiltinLoader::GetCodeCache(
158-
const char* id) const {
159-
RwLock::ScopedReadLock lock(code_cache_->mutex);
160-
const auto it = code_cache_->map.find(id);
161-
if (it == code_cache_->map.end()) {
162-
// The module has not been compiled before.
163-
return nullptr;
164-
}
165-
return it->second.get();
166-
}
167-
168157
#ifdef NODE_BUILTIN_MODULES_PATH
169158
static std::string OnDiskFileName(const char* id) {
170159
std::string filename = NODE_BUILTIN_MODULES_PATH;
@@ -276,7 +265,7 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
276265
OneByteString(isolate, filename_s.c_str(), filename_s.size());
277266
ScriptOrigin origin(isolate, filename, 0, 0, true);
278267

279-
ScriptCompiler::CachedData* cached_data = nullptr;
268+
BuiltinCodeCacheData cached_data{};
280269
{
281270
// Note: The lock here should not extend into the
282271
// `CompileFunction()` call below, because this function may recurse if
@@ -286,16 +275,18 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
286275
auto cache_it = code_cache_->map.find(id);
287276
if (cache_it != code_cache_->map.end()) {
288277
// Transfer ownership to ScriptCompiler::Source later.
289-
cached_data = cache_it->second.release();
290-
code_cache_->map.erase(cache_it);
278+
cached_data = cache_it->second;
291279
}
292280
}
293281

294-
const bool has_cache = cached_data != nullptr;
282+
const bool has_cache = cached_data.data != nullptr;
295283
ScriptCompiler::CompileOptions options =
296284
has_cache ? ScriptCompiler::kConsumeCodeCache
297285
: ScriptCompiler::kEagerCompile;
298-
ScriptCompiler::Source script_source(source, origin, cached_data);
286+
ScriptCompiler::Source script_source(
287+
source,
288+
origin,
289+
has_cache ? cached_data.AsCachedData().release() : nullptr);
299290

300291
per_process::Debug(DebugCategory::CODE_CACHE,
301292
"Compiling %s %s code cache\n",
@@ -342,14 +333,19 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
342333
: "is accepted");
343334
}
344335

345-
// Generate new cache for next compilation
346-
std::unique_ptr<ScriptCompiler::CachedData> new_cached_data(
347-
ScriptCompiler::CreateCodeCacheForFunction(fun));
348-
CHECK_NOT_NULL(new_cached_data);
349-
350-
{
351-
RwLock::ScopedLock lock(code_cache_->mutex);
352-
code_cache_->map[id] = std::move(new_cached_data);
336+
if (*result == Result::kWithoutCache) {
337+
// We failed to accept this cache, maybe because it was rejected, maybe
338+
// because it wasn't present. Either way, we'll attempt to replace this
339+
// code cache info with a new one.
340+
std::shared_ptr<ScriptCompiler::CachedData> new_cached_data(
341+
ScriptCompiler::CreateCodeCacheForFunction(fun));
342+
CHECK_NOT_NULL(new_cached_data);
343+
344+
{
345+
RwLock::ScopedLock lock(code_cache_->mutex);
346+
code_cache_->map.insert_or_assign(
347+
id, BuiltinCodeCacheData(std::move(new_cached_data)));
348+
}
353349
}
354350

355351
return scope.Escape(fun);
@@ -499,9 +495,7 @@ bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
499495
void BuiltinLoader::CopyCodeCache(std::vector<CodeCacheInfo>* out) const {
500496
RwLock::ScopedReadLock lock(code_cache_->mutex);
501497
for (auto const& item : code_cache_->map) {
502-
out->push_back(
503-
{item.first,
504-
{item.second->data, item.second->data + item.second->length}});
498+
out->push_back({item.first, item.second});
505499
}
506500
}
507501

@@ -510,12 +504,7 @@ void BuiltinLoader::RefreshCodeCache(const std::vector<CodeCacheInfo>& in) {
510504
code_cache_->map.reserve(in.size());
511505
DCHECK(code_cache_->map.empty());
512506
for (auto const& item : in) {
513-
auto result = code_cache_->map.emplace(
514-
item.id,
515-
std::make_unique<v8::ScriptCompiler::CachedData>(
516-
item.data.data(),
517-
item.data.size(),
518-
v8::ScriptCompiler::CachedData::BufferNotOwned));
507+
auto result = code_cache_->map.emplace(item.id, item.data);
519508
USE(result.second);
520509
DCHECK(result.second);
521510
}

src/node_builtins.h

+42-7
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,55 @@ class Realm;
2626

2727
namespace builtins {
2828

29+
class BuiltinCodeCacheData {
30+
public:
31+
BuiltinCodeCacheData() : data(nullptr), length(0), owning_ptr(nullptr) {}
32+
33+
explicit BuiltinCodeCacheData(
34+
std::shared_ptr<v8::ScriptCompiler::CachedData> cached_data)
35+
: data(cached_data->data),
36+
length(cached_data->length),
37+
owning_ptr(cached_data) {}
38+
39+
explicit BuiltinCodeCacheData(
40+
std::shared_ptr<std::vector<uint8_t>> cached_data)
41+
: data(cached_data->data()),
42+
length(cached_data->size()),
43+
owning_ptr(cached_data) {}
44+
45+
BuiltinCodeCacheData(const uint8_t* data, size_t length)
46+
: data(data), length(length), owning_ptr(nullptr) {}
47+
48+
const uint8_t* data;
49+
size_t length;
50+
51+
// Returns a v8::ScriptCompiler::CachedData corresponding to this
52+
// BuiltinCodeCacheData. The lifetime of the returned
53+
// v8::ScriptCompiler::CachedData must not outlive that of the data.
54+
std::unique_ptr<v8::ScriptCompiler::CachedData> AsCachedData() {
55+
return std::make_unique<v8::ScriptCompiler::CachedData>(
56+
data, length, v8::ScriptCompiler::CachedData::BufferNotOwned);
57+
}
58+
59+
private:
60+
// If not null, represents a pointer which owns data. Otherwise indicates
61+
// that data has static lifetime.
62+
std::shared_ptr<void> owning_ptr;
63+
};
64+
65+
struct CodeCacheInfo {
66+
std::string id;
67+
BuiltinCodeCacheData data;
68+
};
69+
2970
using BuiltinSourceMap = std::map<std::string, UnionBytes>;
3071
using BuiltinCodeCacheMap =
31-
std::unordered_map<std::string,
32-
std::unique_ptr<v8::ScriptCompiler::CachedData>>;
72+
std::unordered_map<std::string, BuiltinCodeCacheData>;
3373

3474
// Generated by tools/js2c.py as node_javascript.cc
3575
void RegisterExternalReferencesForInternalizedBuiltinCode(
3676
ExternalReferenceRegistry* registry);
3777

38-
struct CodeCacheInfo {
39-
std::string id;
40-
std::vector<uint8_t> data;
41-
};
42-
4378
// Handles compilation and caching of built-in JavaScript modules and
4479
// bootstrap scripts, whose source are bundled into the binary as static data.
4580
class NODE_EXTERN_PRIVATE BuiltinLoader {

src/node_snapshotable.cc

+17-11
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const uint32_t SnapshotData::kMagic;
5353
std::ostream& operator<<(std::ostream& output,
5454
const builtins::CodeCacheInfo& info) {
5555
output << "<builtins::CodeCacheInfo id=" << info.id
56-
<< ", size=" << info.data.size() << ">\n";
56+
<< ", length=" << info.data.length << ">\n";
5757
return output;
5858
}
5959

@@ -207,7 +207,11 @@ template <>
207207
builtins::CodeCacheInfo SnapshotDeserializer::Read() {
208208
Debug("Read<builtins::CodeCacheInfo>()\n");
209209

210-
builtins::CodeCacheInfo result{ReadString(), ReadVector<uint8_t>()};
210+
std::string id = ReadString();
211+
auto owning_ptr =
212+
std::make_shared<std::vector<uint8_t>>(ReadVector<uint8_t>());
213+
builtins::BuiltinCodeCacheData code_cache_data{std::move(owning_ptr)};
214+
builtins::CodeCacheInfo result{id, code_cache_data};
211215

212216
if (is_debug) {
213217
std::string str = ToStr(result);
@@ -217,14 +221,16 @@ builtins::CodeCacheInfo SnapshotDeserializer::Read() {
217221
}
218222

219223
template <>
220-
size_t SnapshotSerializer::Write(const builtins::CodeCacheInfo& data) {
224+
size_t SnapshotSerializer::Write(const builtins::CodeCacheInfo& info) {
221225
Debug("\nWrite<builtins::CodeCacheInfo>() id = %s"
222-
", size=%d\n",
223-
data.id.c_str(),
224-
data.data.size());
226+
", length=%d\n",
227+
info.id.c_str(),
228+
info.data.length);
225229

226-
size_t written_total = WriteString(data.id);
227-
written_total += WriteVector<uint8_t>(data.data);
230+
size_t written_total = WriteString(info.id);
231+
232+
written_total += WriteArithmetic<size_t>(info.data.length);
233+
written_total += WriteArithmetic(info.data.data, info.data.length);
228234

229235
Debug("Write<builtins::CodeCacheInfo>() wrote %d bytes\n", written_total);
230236
return written_total;
@@ -734,15 +740,15 @@ static std::string FormatSize(size_t size) {
734740
static void WriteStaticCodeCacheData(std::ostream* ss,
735741
const builtins::CodeCacheInfo& info) {
736742
*ss << "static const uint8_t " << GetCodeCacheDefName(info.id) << "[] = {\n";
737-
WriteVector(ss, info.data.data(), info.data.size());
743+
WriteVector(ss, info.data.data, info.data.length);
738744
*ss << "};";
739745
}
740746

741747
static void WriteCodeCacheInitializer(std::ostream* ss, const std::string& id) {
742748
std::string def_name = GetCodeCacheDefName(id);
743749
*ss << " { \"" << id << "\",\n";
744750
*ss << " {" << def_name << ",\n";
745-
*ss << " " << def_name << " + arraysize(" << def_name << "),\n";
751+
*ss << " arraysize(" << def_name << "),\n";
746752
*ss << " }\n";
747753
*ss << " },\n";
748754
}
@@ -954,7 +960,7 @@ ExitCode SnapshotBuilder::CreateSnapshot(SnapshotData* out,
954960
}
955961
env->builtin_loader()->CopyCodeCache(&(out->code_cache));
956962
for (const auto& item : out->code_cache) {
957-
std::string size_str = FormatSize(item.data.size());
963+
std::string size_str = FormatSize(item.data.length);
958964
per_process::Debug(DebugCategory::MKSNAPSHOT,
959965
"Generated code cache for %d: %s\n",
960966
item.id.c_str(),

0 commit comments

Comments
 (0)