Significantly reduce size of block_t

A `block_t` instance is allocated for each live block type in memory when
executing a script or snippet of fish code. While many of the items in a
`block_t` class are specific to a particular type of block, the overhead of
`maybe_t<event_t>` that's unused except in the relatively extremely rare case of
an event block is more significant than the rest, given that 88 out of the 216
bytes of a `block_t` are set aside for this field that is rarely used.

This patch reorders the `block_t` members by order of decreasing alignment,
bringing down the size to 208 bytes, then changes `maybe_t<event_t>` to
`shared_ptr<event_t>` instead of allocating room for the event on the stack.
This brings down the runtime memory size of a `block_t` to 136 bytes for a 37%
reduction in size.

I would like to investigate using inheritance and virtual methods to have a
`block_t` only include the values that actually make sense for the block rather
than always allocating some sort of storage for them and then only sometimes
using it. In addition to further reducing the memory, I think this could also be
a safer and saner approach overall, as it would make it very clear when and
where we can expect each block_type_type_t-dependent member to be present and
hold a value.
This commit is contained in:
Mahmoud Al-Qudsi 2022-10-24 21:04:17 -05:00
parent 44c9c51841
commit 84b53b4cae
2 changed files with 25 additions and 18 deletions

View file

@ -740,7 +740,7 @@ block_t block_t::if_block() { return block_t(block_type_t::if_block); }
block_t block_t::event_block(event_t evt) {
block_t b{block_type_t::event};
b.event = std::move(evt);
b.event.reset(new event_t(std::move(evt)));
return b;
}

View file

@ -38,7 +38,7 @@ inline bool event_block_list_blocks_type(const event_blockage_list_t &ebls) {
}
/// Types of blocks.
enum class block_type_t {
enum class block_type_t : uint16_t {
while_block, /// While loop block
for_block, /// For loop block
if_block, /// If block
@ -63,40 +63,47 @@ enum class loop_status_t {
/// block_t represents a block of commands.
class block_t {
private:
/// Construct from a block type.
explicit block_t(block_type_t t);
/// Type of block.
const block_type_t block_type;
public:
/// Name of file that created this block.
filename_ref_t src_filename{};
/// Line number where this block was created.
int src_lineno{0};
/// Whether we should pop the environment variable stack when we're popped off of the block
/// stack.
bool wants_pop_env{false};
// If this is a function block, the function name. Otherwise empty.
wcstring function_name{};
/// List of event blocks.
event_blockage_list_t event_blocks{};
// If this is a function block, the function name and arguments.
// Otherwise empty.
wcstring function_name{};
// If this is a function block, the function args. Otherwise empty.
wcstring_list_t function_args{};
/// Name of file that created this block.
filename_ref_t src_filename{};
// If this is an event block, the event. Otherwise ignored.
std::shared_ptr<event_t> event;
// If this is a source block, the source'd file, interned.
// Otherwise nothing.
filename_ref_t sourced_file{};
// If this is an event block, the event. Otherwise ignored.
maybe_t<event_t> event;
/// Line number where this block was created.
int src_lineno{0};
block_type_t type() const { return this->block_type; }
private:
/// Type of block.
const block_type_t block_type;
public:
/// Whether we should pop the environment variable stack when we're popped off of the block
/// stack.
bool wants_pop_env{false};
/// Description of the block, for debugging.
wcstring description() const;
block_type_t type() const { return this->block_type; }
/// \return if we are a function call (with or without shadowing).
bool is_function_call() const {
return type() == block_type_t::function_call ||