-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
src: register external references for source code
Currently we use external strings for internalized builtin source code. However when a snapshot is taken, any external string whose resource is not registered is flattened into a SeqString (see ref). The result is that any module source code stored in the snapshot does not use external strings after deserialization. This patch registers an external string resource for each internalized builtin's source. The savings are substantial: ~1.9 MB of heap memory per isolate, or ~44% of an otherwise empty isolate's heap usage: ```bash $ node --expose-gc -p 'gc(),process.memoryUsage().heapUsed' 4190968 $ ./node --expose-gc -p 'gc(),process.memoryUsage().heapUsed' 2327536 ``` The savings can be even higher for user snapshots which may include more internal modules. Doing this with the existing UnionBytes abstraction was tricky, because UnionBytes only creates an external string resource when ToStringChecked is called. However we need to collate a list of external resources before isolate construction. UnionBytes can also be deallocated, which isn't ideal since registering an external string resource which is later deallocated would be very bad. Rather than further complicate UnionBytes, we introduce a new class called EternalBytes which assumes that the data has static lifetime and creates a single external string resource on construction. It reuses this original external string resource across V8 isolates by simply ignoring Dispose calls from V8. In order to distinguish between EternalBytes and UnionBytes, we bifurcate the sources map into two maps: the internalized builtins map (which is never modified) and the sources map (which can be changed through externalized builtins or by the embedder). Refs: https://github.com/v8/v8/blob/d2c8fbe9ccd1a6ce5591bb7dd319c3c00d6bf489/src/snapshot/serializer.cc#L633
- Loading branch information
Showing
7 changed files
with
195 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
#ifndef SRC_NODE_ETERNAL_BYTES_H_ | ||
#define SRC_NODE_ETERNAL_BYTES_H_ | ||
|
||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
||
// A pointer to const uint8_t* or const uint16_t* data that can be turned into | ||
// external v8::String when given an isolate. Unlike UnionBytes, this assumes | ||
// that the underlying buffer is eternal (will not be garbage collected) and | ||
// reuses the same v8::String::ExternalStringResourceBase for all materialized | ||
// strings. This allows the resource to be registered as an external reference | ||
// for snapshotting. | ||
|
||
#include <variant> | ||
#include "v8.h" | ||
|
||
namespace node { | ||
|
||
class EternalBytes; | ||
|
||
template <typename Char, typename IChar, typename Base> | ||
class EternalExternalByteResource : public Base { | ||
static_assert(sizeof(IChar) == sizeof(Char), | ||
"incompatible interface and internal pointers"); | ||
|
||
public: | ||
explicit EternalExternalByteResource(const Char* data, size_t length) | ||
: data_(data), length_(length) {} | ||
|
||
const IChar* data() const override { | ||
return reinterpret_cast<const IChar*>(data_); | ||
} | ||
size_t length() const override { return length_; } | ||
|
||
void Dispose() override { | ||
// Do nothing. This class is owned by the EternalBytes instance and so | ||
// should not be destroyed. It may also be in use by other external strings | ||
// besides the one which was collected. | ||
} | ||
|
||
EternalExternalByteResource(const EternalExternalByteResource&) = delete; | ||
EternalExternalByteResource& operator=(const EternalExternalByteResource&) = | ||
delete; | ||
|
||
friend class EternalBytes; | ||
|
||
private: | ||
const Char* data_; | ||
const size_t length_; | ||
}; | ||
|
||
using EternalExternalOneByteResource = | ||
EternalExternalByteResource<uint8_t, | ||
char, | ||
v8::String::ExternalOneByteStringResource>; | ||
using EternalExternalTwoByteResource = | ||
EternalExternalByteResource<uint16_t, | ||
uint16_t, | ||
v8::String::ExternalStringResource>; | ||
|
||
namespace { | ||
template <class... Ts> | ||
struct overloaded : Ts... { | ||
using Ts::operator()...; | ||
}; | ||
template <class... Ts> | ||
overloaded(Ts...) -> overloaded<Ts...>; | ||
} // namespace | ||
|
||
class EternalBytes { | ||
public: | ||
EternalBytes(const uint8_t* data, size_t length) | ||
: resource_(new EternalExternalOneByteResource(data, length)){}; | ||
EternalBytes(const uint16_t* data, size_t length) | ||
: resource_(new EternalExternalTwoByteResource(data, length)){}; | ||
|
||
EternalBytes(const EternalBytes&) = default; | ||
EternalBytes& operator=(const EternalBytes&) = default; | ||
EternalBytes(EternalBytes&&) = default; | ||
EternalBytes& operator=(EternalBytes&&) = default; | ||
|
||
bool IsOneByte() const { | ||
return std::holds_alternative<EternalExternalOneByteResource*>(resource_); | ||
} | ||
|
||
const v8::String::ExternalStringResourceBase* AsResource() const { | ||
return std::visit( | ||
overloaded{ | ||
[](EternalExternalOneByteResource* resource) { | ||
return static_cast<const v8::String::ExternalStringResourceBase*>( | ||
resource); | ||
}, | ||
[](EternalExternalTwoByteResource* resource) { | ||
return static_cast<const v8::String::ExternalStringResourceBase*>( | ||
resource); | ||
}}, | ||
resource_); | ||
} | ||
|
||
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) const { | ||
return std::visit( | ||
overloaded{[=](EternalExternalOneByteResource* resource) { | ||
return v8::String::NewExternalOneByte(isolate, resource) | ||
.ToLocalChecked(); | ||
}, | ||
[=](EternalExternalTwoByteResource* resource) { | ||
return v8::String::NewExternalTwoByte(isolate, resource) | ||
.ToLocalChecked(); | ||
}}, | ||
resource_); | ||
} | ||
|
||
private: | ||
const std::variant<EternalExternalOneByteResource*, | ||
EternalExternalTwoByteResource*> | ||
resource_; | ||
}; | ||
|
||
} // namespace node | ||
|
||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
||
#endif // SRC_NODE_ETERNAL_BYTES_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters