Skip to content

Commit

Permalink
18781: Improves performance of accumulation opcodes, more preparation…
Browse files Browse the repository at this point in the history
… for entity write multithreading, fixes accum type bug where list would sometimes be used instead of original type (#49)
  • Loading branch information
howsohazard authored Jan 2, 2024
1 parent 334b04a commit fd5acc2
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 223 deletions.
108 changes: 59 additions & 49 deletions src/Amalgam/entity/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,22 +547,16 @@ size_t Entity::GetEstimatedUsedDeepSizeInBytes()
return total_size;
}

EvaluableNode::LabelsAssocType Entity::RebuildLabelIndex()
void Entity::RebuildLabelIndex()
{
auto [new_labels, renormalized] = EvaluableNodeTreeManipulation::RetrieveLabelIndexesFromTreeAndNormalize(evaluableNodeManager.GetRootNode());
auto [new_labels, collision_free] = EvaluableNodeTreeManipulation::RetrieveLabelIndexesFromTreeAndNormalize(evaluableNodeManager.GetRootNode());

//update references (create new ones before destroying old ones so they do not need to be recreated)
string_intern_pool.CreateStringReferences(new_labels, [](auto l) { return l.first; } );
string_intern_pool.DestroyStringReferences(labelIndex, [](auto l) { return l.first; });

//let the destructor of new_labels deallocate the old labelIndex
std::swap(labelIndex, new_labels);

if(renormalized)
new_labels.clear();

//new_labels now holds the previous labels
return new_labels;
}

StringInternPool::StringID Entity::AddContainedEntity(Entity *t, StringInternPool::StringID id_sid, std::vector<EntityWriteListener *> *write_listeners)
Expand Down Expand Up @@ -697,7 +691,6 @@ StringInternPool::StringID Entity::AddContainedEntity(Entity *t, std::string id_
return t->idStringId;
}


void Entity::RemoveContainedEntity(StringInternPool::StringID id, std::vector<EntityWriteListener *> *write_listeners)
{
if(!hasContainedEntities)
Expand Down Expand Up @@ -896,74 +889,91 @@ void Entity::SetRoot(EvaluableNode *_code, bool allocated_with_entity_enm, Evalu
}
}

void Entity::SetRoot(std::string &code_string, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier, std::vector<EntityWriteListener *> *write_listeners)
void Entity::SetRoot(std::string &code_string, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier,
std::vector<EntityWriteListener *> *write_listeners)
{
EvaluableNodeReference new_code = Parser::Parse(code_string, &evaluableNodeManager);
SetRoot(new_code, true, metadata_modifier, write_listeners);
}

void Entity::AccumRoot(EvaluableNodeReference accum_code, bool allocated_with_entity_enm, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier, std::vector<EntityWriteListener *> *write_listeners)
void Entity::AccumRoot(EvaluableNodeReference accum_code, bool allocated_with_entity_enm,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier,
std::vector<EntityWriteListener *> *write_listeners)
{
if( !(allocated_with_entity_enm && metadata_modifier == EvaluableNodeManager::ENMM_NO_CHANGE))
if(!(allocated_with_entity_enm && metadata_modifier == EvaluableNodeManager::ENMM_NO_CHANGE))
accum_code = evaluableNodeManager.DeepAllocCopy(accum_code, metadata_modifier);

bool accum_has_labels = EvaluableNodeTreeManipulation::DoesTreeContainLabels(accum_code);
auto [new_labels, no_label_collisions] = EvaluableNodeTreeManipulation::RetrieveLabelIndexesFromTree(accum_code);

EvaluableNode *previous_root = evaluableNodeManager.GetRootNode();
EvaluableNodeReference new_root = AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeReference(previous_root, true), accum_code, &evaluableNodeManager);

//need to check if still cycle free as it may no longer be
EvaluableNodeManager::UpdateFlagsForNodeTree(new_root);
//before accumulating, check to see if flags will need to be updated
bool node_flags_need_update = false;
if(previous_root == nullptr)
{
node_flags_need_update = true;
}
else
{
//need to update node flags if new_root is cycle free, but accum_node isn't
if(previous_root->GetNeedCycleCheck() && (accum_code != nullptr && !accum_code->GetNeedCycleCheck()))
node_flags_need_update = true;

//need to update node flags if new_root is idempotent and accum_node isn't
if(previous_root->GetIsIdempotent() && (accum_code != nullptr && !accum_code->GetIsIdempotent()))
node_flags_need_update = true;
}

//accum, but can't treat as unique in case any other thread is accessing the data
EvaluableNodeReference new_root = AccumulateEvaluableNodeIntoEvaluableNode(
EvaluableNodeReference(previous_root, false), accum_code, &evaluableNodeManager);

if(new_root != previous_root)
{
//keep reference for current root (mainly in case a new node was added if the entity were previously empty)
//keep reference for current root before setting and freeing
evaluableNodeManager.KeepNodeReference(new_root);

evaluableNodeManager.SetRootNode(new_root);

//free current root reference
evaluableNodeManager.FreeNodeReference(previous_root);
}

size_t num_root_labels_to_update = 0;
if(new_root != nullptr)
num_root_labels_to_update = new_root->GetNumLabels();
//optimistically create references for the new labels, delete them if find collisions
string_intern_pool.CreateStringReferences(new_labels, [](auto l) { return l.first; });

//attempt to insert the new labels as long as there's no collision
for(auto &[label, value] : new_labels)
{
auto [new_entry, inserted] = labelIndex.emplace(label, value);
if(!inserted)
{
string_intern_pool.DestroyStringReference(label);
no_label_collisions = false;
}
}

EntityQueryCaches *container_caches = GetContainerQueryCaches();

if(accum_has_labels)
//can do a much more straightforward update if there are no label collisions and the root has no labels
if(no_label_collisions && new_root->GetNumLabels() == 0)
{
EvaluableNode::LabelsAssocType prev_labels = RebuildLabelIndex();

//if have all new labels or RebuildLabelIndex had to renormalize (in which case prev_labels will be empty)
// then update all labels just in case
if(prev_labels.size() == 0 && labelIndex.size() > 0)
{
if(container_caches != nullptr)
container_caches->UpdateAllEntityLabels(this, GetEntityIndexOfContainer());
if(node_flags_need_update)
EvaluableNodeManager::UpdateFlagsForNodeTree(new_root);

//root labels have been updated
num_root_labels_to_update = 0;
}
else //clean rebuild
{
if(container_caches != nullptr)
container_caches->UpdateEntityLabelsAddedOrChanged(this, GetEntityIndexOfContainer(),
prev_labels, labelIndex);
}
if(container_caches != nullptr)
container_caches->UpdateEntityLabels(this, GetEntityIndexOfContainer(), new_labels);
}

//if any root labels left to update, then update them
if(num_root_labels_to_update > 0)
else //either collisions or root node has at least one label
{
//only need to update labels on root
for(size_t i = 0; i < num_root_labels_to_update; i++)
if(!no_label_collisions)
{
auto label_sid = new_root->GetLabelStringId(i);
if(container_caches != nullptr)
container_caches->UpdateEntityLabel(this, GetEntityIndexOfContainer(), label_sid);
//all new labels have already been inserted
auto [new_label_index, collision_free] = EvaluableNodeTreeManipulation::RetrieveLabelIndexesFromTreeAndNormalize(
evaluableNodeManager.GetRootNode());

std::swap(labelIndex, new_labels);
}

if(container_caches != nullptr)
container_caches->UpdateAllEntityLabels(this, GetEntityIndexOfContainer());
}

if(write_listeners != nullptr)
Expand Down
35 changes: 21 additions & 14 deletions src/Amalgam/entity/Entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ class Entity

Entity();

//create Entity from existing code, rand_state is the current state of the random number generator, modifying labels as specified
Entity(Entity *_container, std::string &code_string, const std::string &rand_state, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE);
Entity(Entity *_container, EvaluableNode *_root, const std::string &rand_state, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE);
//create Entity from existing code, rand_state is the current state of the random number generator,
// modifying labels as specified
Entity(Entity *_container, std::string &code_string, const std::string &rand_state,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE);
Entity(Entity *_container, EvaluableNode *_root, const std::string &rand_state,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE);

//Creates a new Entity as a copy of the Entity passed in; everything is identical except for the time created and id
Entity(Entity *t);
Expand Down Expand Up @@ -193,10 +196,8 @@ class Entity
std::pair<bool, bool> SetValuesAtLabels(EvaluableNodeReference new_label_values, bool accum_values, bool direct_set,
std::vector<EntityWriteListener *> *write_listeners, size_t *num_new_nodes_allocated, bool on_self, bool copy_entity);

//Rebuilds label index for retrieval
// returns the previous label index prior to rebuild; if the label index had to be rebuilt from scratch
// due to a label collision, then the previous label index will be empty
EvaluableNode::LabelsAssocType RebuildLabelIndex();
//Rebuilds label index
void RebuildLabelIndex();

//Returns the id for this Entity
inline const std::string &GetId()
Expand Down Expand Up @@ -386,15 +387,21 @@ class Entity
}

//Sets the code and recreates the index, modifying labels as specified
// if allocated_with_entity_enm is false, then it will copy the tree into the entity's EvaluableNodeManager, otherwise it will just assume it is already available
// write_listeners is optional, and if specified, will log the event
void SetRoot(EvaluableNode *_code, bool allocated_with_entity_enm, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE, std::vector<EntityWriteListener *> *write_listeners = nullptr);
void SetRoot(std::string &code_string, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE, std::vector<EntityWriteListener *> *write_listeners = nullptr);
//if allocated_with_entity_enm is false, then it will copy the tree into the entity's EvaluableNodeManager, otherwise it will just assume it is already available
//write_listeners is optional, and if specified, will log the event
void SetRoot(EvaluableNode *_code, bool allocated_with_entity_enm,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE,
std::vector<EntityWriteListener *> *write_listeners = nullptr);
void SetRoot(std::string &code_string,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE,
std::vector<EntityWriteListener *> *write_listeners = nullptr);

//accumulates the code and recreates the index, modifying labels as specified
// if allocated_with_entity_enm is false, then it will copy the tree into the entity's EvaluableNodeManager, otherwise it will just assume it is already available
// write_listeners is optional, and if specified, will log the event
void AccumRoot(EvaluableNodeReference _code, bool allocated_with_entity_enm, EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE, std::vector<EntityWriteListener *> *write_listeners = nullptr);
//if allocated_with_entity_enm is false, then it will copy the tree into the entity's EvaluableNodeManager, otherwise it will just assume it is already available
//write_listeners is optional, and if specified, will log the event
void AccumRoot(EvaluableNodeReference _code, bool allocated_with_entity_enm,
EvaluableNodeManager::EvaluableNodeMetadataModifier metadata_modifier = EvaluableNodeManager::ENMM_NO_CHANGE,
std::vector<EntityWriteListener *> *write_listeners = nullptr);

//collects garbage on evaluableNodeManager
#ifdef MULTITHREAD_SUPPORT
Expand Down
19 changes: 0 additions & 19 deletions src/Amalgam/entity/EntityQueryCaches.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,6 @@ class EntityQueryCaches
sbfds.UpdateEntityLabel(entity, entity_index, label_id);
}

//like UpdateEntityLabels, but only updates labels for the keys of labels_updated that are not in labels_previous
// or where the value has changed
inline void UpdateEntityLabelsAddedOrChanged(Entity *entity, size_t entity_index,
EvaluableNode::AssocType &labels_previous, EvaluableNode::AssocType &labels_updated)
{
#if defined(MULTITHREAD_SUPPORT) || defined(MULTITHREAD_INTERFACE)
Concurrency::WriteLock write_lock(mutex);
#endif

for(auto &[label_id, label] : labels_updated)
{
auto prev_entry = labels_previous.find(label_id);

//if not found or different, need to update the label
if(prev_entry == end(labels_previous) || prev_entry->second != label)
sbfds.UpdateEntityLabel(entity, entity_index, label_id);
}
}

//like UpdateAllEntityLabels, but only updates labels for label_updated
inline void UpdateEntityLabel(Entity *entity, size_t entity_index, StringInternPool::StringID label_updated)
{
Expand Down
37 changes: 21 additions & 16 deletions src/Amalgam/evaluablenode/EvaluableNodeTreeFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef
if(value_destination_node == nullptr)
return variable_value_node;

//set up initial flags
bool result_unique = (value_destination_node.unique && variable_value_node.unique);
bool result_need_cycle_check = value_destination_node->GetNeedCycleCheck();
if(!variable_value_node.unique || (variable_value_node != nullptr && variable_value_node->GetNeedCycleCheck()))
result_need_cycle_check = true;
bool result_idempontent = (value_destination_node->GetIsIdempotent() && (variable_value_node == nullptr || variable_value_node->GetIsIdempotent()));

//if the value is unique, then can just edit in place
if(value_destination_node.unique)
{
Expand Down Expand Up @@ -389,8 +396,9 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef

enm->FreeNodeIfPossible(variable_value_node);

value_destination_node->SetNeedCycleCheck(true);
value_destination_node.unique = (value_destination_node.unique && variable_value_node.unique);
value_destination_node->SetNeedCycleCheck(result_need_cycle_check);
value_destination_node->SetIsIdempotent(result_idempontent);
value_destination_node.unique = result_unique;
}
else if(value_destination_node->IsStringValue())
{
Expand Down Expand Up @@ -432,8 +440,9 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef
value_destination_node->AppendOrderedChildNode(variable_value_node);
}

value_destination_node->SetNeedCycleCheck(true);
value_destination_node.unique = (value_destination_node.unique && variable_value_node.unique);
value_destination_node->SetNeedCycleCheck(result_need_cycle_check);
value_destination_node->SetIsIdempotent(result_idempontent);
value_destination_node.unique = result_unique;
}

return value_destination_node;
Expand All @@ -448,18 +457,15 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef
}
else if(value_destination_node->IsAssociativeArray())
{
EvaluableNode *new_list = enm->AllocNode(value_destination_node->GetType());
EvaluableNode *new_list = enm->AllocNode(value_destination_node);

if(EvaluableNode::IsAssociativeArray(variable_value_node))
{
new_list->ReserveMappedChildNodes(value_destination_node->GetMappedChildNodes().size()
+ variable_value_node->GetMappedChildNodesReference().size());
new_list->SetMappedChildNodes(value_destination_node->GetMappedChildNodes(), true);
new_list->AppendMappedChildNodes(variable_value_node->GetMappedChildNodes());
}
else if(variable_value_node != nullptr) //treat ordered pairs as new entries as long as not nullptr
{
new_list->ReserveMappedChildNodes(value_destination_node->GetMappedChildNodes().size() + variable_value_node->GetOrderedChildNodes().size() / 2);
new_list->SetMappedChildNodes(value_destination_node->GetMappedChildNodes(), true);
//iterate as long as pairs exist
auto &vvn_ocn = variable_value_node->GetOrderedChildNodes();
Expand All @@ -472,8 +478,9 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef

enm->FreeNodeIfPossible(variable_value_node);

value_destination_node.SetReference(new_list, value_destination_node.unique && variable_value_node.unique);
value_destination_node->SetNeedCycleCheck(true);
value_destination_node->SetNeedCycleCheck(result_need_cycle_check);
value_destination_node->SetIsIdempotent(result_idempontent);
value_destination_node.SetReference(new_list, result_unique);
}
else if(value_destination_node->IsStringValue())
{
Expand All @@ -488,12 +495,11 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef
}
else //add ordered child node
{
EvaluableNode *new_list = enm->AllocNode(ENT_LIST);
EvaluableNode *new_list = enm->AllocNode(value_destination_node);
if(EvaluableNode::IsAssociativeArray(variable_value_node))
{
//expand out into pairs
new_list->ReserveOrderedChildNodes(value_destination_node->GetOrderedChildNodes().size() + 2 * variable_value_node->GetMappedChildNodes().size());
new_list->AppendOrderedChildNodes(value_destination_node->GetOrderedChildNodes());
for(auto &[cn_id, cn] : variable_value_node->GetMappedChildNodes())
{
new_list->AppendOrderedChildNode(enm->AllocNode(ENT_STRING, cn_id));
Expand All @@ -505,20 +511,19 @@ EvaluableNodeReference AccumulateEvaluableNodeIntoEvaluableNode(EvaluableNodeRef
else if(EvaluableNode::IsOrderedArray(variable_value_node))
{
new_list->ReserveOrderedChildNodes(value_destination_node->GetOrderedChildNodes().size() + variable_value_node->GetOrderedChildNodes().size());
new_list->AppendOrderedChildNodes(value_destination_node->GetOrderedChildNodes());
new_list->AppendOrderedChildNodes(variable_value_node->GetOrderedChildNodes());

enm->FreeNodeIfPossible(variable_value_node);
}
else //just append one value
{
new_list->ReserveOrderedChildNodes(value_destination_node->GetOrderedChildNodes().size() + 1);
new_list->AppendOrderedChildNodes(value_destination_node->GetOrderedChildNodes());
new_list->AppendOrderedChildNode(variable_value_node);
}

value_destination_node.SetReference(new_list, value_destination_node.unique &&variable_value_node.unique);
value_destination_node->SetNeedCycleCheck(true);
value_destination_node->SetNeedCycleCheck(result_need_cycle_check);
value_destination_node->SetIsIdempotent(result_idempontent);
value_destination_node.SetReference(new_list, result_unique);
}

return value_destination_node;
Expand Down
Loading

0 comments on commit fd5acc2

Please sign in to comment.