Optimize Object::GetObject. Introduce an array of aggregates and sort is by access frequency.
This commit is contained in:
@@ -28,6 +28,8 @@
|
||||
#include "string.h"
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE ("Object");
|
||||
|
||||
@@ -40,28 +42,23 @@ namespace ns3 {
|
||||
NS_OBJECT_ENSURE_REGISTERED (Object);
|
||||
|
||||
Object::AggregateIterator::AggregateIterator ()
|
||||
: m_first (0),
|
||||
: m_object (0),
|
||||
m_current (0)
|
||||
{}
|
||||
|
||||
bool
|
||||
Object::AggregateIterator::HasNext (void) const
|
||||
{
|
||||
if (m_current != 0 && m_current->m_next != PeekPointer (m_first))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return m_current < m_object->m_aggregates->n;
|
||||
}
|
||||
Ptr<const Object>
|
||||
Object::AggregateIterator::Next (void)
|
||||
{
|
||||
m_current = m_current->m_next;
|
||||
return m_current;
|
||||
return m_object->m_aggregates->buffer[m_current];
|
||||
}
|
||||
Object::AggregateIterator::AggregateIterator (Ptr<const Object> first)
|
||||
: m_first (first),
|
||||
m_current (first)
|
||||
Object::AggregateIterator::AggregateIterator (Ptr<const Object> object)
|
||||
: m_object (object),
|
||||
m_current (0)
|
||||
{}
|
||||
|
||||
|
||||
@@ -85,18 +82,45 @@ Object::Object ()
|
||||
: m_count (1),
|
||||
m_tid (Object::GetTypeId ()),
|
||||
m_disposed (false),
|
||||
m_next (this)
|
||||
{}
|
||||
m_aggregates ((struct Aggregates *)malloc (sizeof (struct Aggregates))),
|
||||
m_getObjectCount (0)
|
||||
{
|
||||
m_aggregates->n = 1;
|
||||
m_aggregates->buffer[0] = this;
|
||||
}
|
||||
Object::~Object ()
|
||||
{
|
||||
m_next = 0;
|
||||
// remove this object from the aggregate list
|
||||
uint32_t n = m_aggregates->n;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
if (current == this)
|
||||
{
|
||||
memmove (&m_aggregates->buffer[i],
|
||||
&m_aggregates->buffer[i+1],
|
||||
sizeof (Object *)*(m_aggregates->n - (i+1)));
|
||||
m_aggregates->n--;
|
||||
}
|
||||
}
|
||||
// finally, if all objects have been removed from the list,
|
||||
// delete the aggregate list
|
||||
if (m_aggregates->n == 0)
|
||||
{
|
||||
free (m_aggregates);
|
||||
}
|
||||
m_aggregates = 0;
|
||||
}
|
||||
Object::Object (const Object &o)
|
||||
: m_count (1),
|
||||
m_tid (o.m_tid),
|
||||
m_disposed (false),
|
||||
m_next (this)
|
||||
{}
|
||||
m_aggregates ((struct Aggregates *)malloc (sizeof (struct Aggregates))),
|
||||
m_getObjectCount (0)
|
||||
{
|
||||
m_aggregates->n = 1;
|
||||
m_aggregates->buffer[0] = this;
|
||||
}
|
||||
uint32_t
|
||||
Object::GetReferenceCount (void) const
|
||||
{
|
||||
@@ -112,48 +136,58 @@ Ptr<Object>
|
||||
Object::DoGetObject (TypeId tid) const
|
||||
{
|
||||
NS_ASSERT (CheckLoose ());
|
||||
const Object *currentObject = this;
|
||||
const Object *prevObject = 0;
|
||||
|
||||
uint32_t n = m_aggregates->n;
|
||||
TypeId objectTid = Object::GetTypeId ();
|
||||
do {
|
||||
NS_ASSERT (currentObject != 0);
|
||||
TypeId cur = currentObject->GetInstanceTypeId ();
|
||||
while (cur != tid && cur != objectTid)
|
||||
{
|
||||
cur = cur.GetParent ();
|
||||
}
|
||||
if (cur == tid)
|
||||
{
|
||||
if (prevObject != 0)
|
||||
{
|
||||
// This is an attempt to 'cache' the result of this lookup.
|
||||
// the idea is that if we perform a lookup for a TypdId on this object,
|
||||
// we are likely to perform the same lookup later so, we re-order
|
||||
// the circular linked-list of objects here by putting the object we
|
||||
// just found at the head of the list. This optimization is
|
||||
// _extremely_ effective in general.
|
||||
const_cast<Object*>(prevObject)->m_next = currentObject->m_next;
|
||||
const_cast<Object*>(currentObject)->m_next = m_next;
|
||||
const_cast<Object*>(this)->m_next = (Object*)currentObject;
|
||||
}
|
||||
return const_cast<Object *> (currentObject);
|
||||
}
|
||||
prevObject = currentObject;
|
||||
currentObject = currentObject->m_next;
|
||||
} while (currentObject != this);
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
TypeId cur = current->GetInstanceTypeId ();
|
||||
while (cur != tid && cur != objectTid)
|
||||
{
|
||||
cur = cur.GetParent ();
|
||||
}
|
||||
if (cur == tid)
|
||||
{
|
||||
// This is an attempt to 'cache' the result of this lookup.
|
||||
// the idea is that if we perform a lookup for a TypeId on this object,
|
||||
// we are likely to perform the same lookup later so, we make sure
|
||||
// that the aggregate array is sorted by the number of accesses
|
||||
// to each object.
|
||||
|
||||
// first, increment the access count
|
||||
current->m_getObjectCount++;
|
||||
// then, update the sort
|
||||
UpdateSortedArray (m_aggregates, i);
|
||||
// finally, return the match
|
||||
return const_cast<Object *> (current);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void
|
||||
Object::Dispose (void)
|
||||
{
|
||||
Object *current = this;
|
||||
do {
|
||||
NS_ASSERT (current != 0);
|
||||
NS_ASSERT (!current->m_disposed);
|
||||
current->DoDispose ();
|
||||
current->m_disposed = true;
|
||||
current = current->m_next;
|
||||
} while (current != this);
|
||||
uint32_t n = m_aggregates->n;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
NS_ASSERT (!current->m_disposed);
|
||||
current->DoDispose ();
|
||||
current->m_disposed = true;
|
||||
}
|
||||
}
|
||||
void
|
||||
Object::UpdateSortedArray (struct Aggregates *aggregates, uint32_t j) const
|
||||
{
|
||||
while (j > 0 &&
|
||||
aggregates->buffer[j]->m_getObjectCount > aggregates->buffer[j-1]->m_getObjectCount)
|
||||
{
|
||||
Object *tmp = aggregates->buffer[j-1];
|
||||
aggregates->buffer[j-1] = aggregates->buffer[j];
|
||||
aggregates->buffer[j] = tmp;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
void
|
||||
Object::AggregateObject (Ptr<Object> o)
|
||||
@@ -171,20 +205,38 @@ Object::AggregateObject (Ptr<Object> o)
|
||||
}
|
||||
|
||||
Object *other = PeekPointer (o);
|
||||
Object *next = m_next;
|
||||
m_next = other->m_next;
|
||||
other->m_next = next;
|
||||
NS_ASSERT (CheckLoose ());
|
||||
NS_ASSERT (o->CheckLoose ());
|
||||
// call NotifyNewAggregate in the listed chain
|
||||
Object *currentObject = this;
|
||||
do
|
||||
// first create the new aggregate buffer.
|
||||
uint32_t total = m_aggregates->n + other->m_aggregates->n;
|
||||
struct Aggregates *aggregates =
|
||||
(struct Aggregates *)malloc (sizeof(struct Aggregates)+(total-1)*sizeof(Object*));
|
||||
aggregates->n = total;
|
||||
memcpy (&aggregates->buffer[0],
|
||||
&m_aggregates->buffer[0],
|
||||
m_aggregates->n*sizeof(Object*));
|
||||
// append the other aggregates in the new buffer
|
||||
for (uint32_t i = 0; i < other->m_aggregates->n; i++)
|
||||
{
|
||||
// the NotifyNewAggregate of the current object implementation
|
||||
// should be called on the next object in the linked chain
|
||||
currentObject->NotifyNewAggregate ();
|
||||
currentObject = currentObject->m_next;
|
||||
} while (currentObject != this);
|
||||
aggregates->buffer[m_aggregates->n+i] = other->m_aggregates->buffer[i];
|
||||
UpdateSortedArray (aggregates, m_aggregates->n + i);
|
||||
}
|
||||
|
||||
// free both aggregate buffers
|
||||
free (m_aggregates);
|
||||
free (other->m_aggregates);
|
||||
|
||||
// Then, assign that buffer to every object
|
||||
uint32_t n = aggregates->n;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = aggregates->buffer[i];
|
||||
current->m_aggregates = aggregates;
|
||||
}
|
||||
// Finally, call NotifyNewAggregate in the listed chain
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
current->NotifyNewAggregate ();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This function must be implemented in the stack that needs to notify
|
||||
@@ -233,14 +285,12 @@ bool
|
||||
Object::CheckLoose (void) const
|
||||
{
|
||||
uint32_t refcount = 0;
|
||||
const Object *current = this;
|
||||
do
|
||||
uint32_t n = m_aggregates->n;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
refcount += current->m_count;
|
||||
current = current->m_next;
|
||||
}
|
||||
while (current != this);
|
||||
|
||||
return (refcount > 0);
|
||||
}
|
||||
|
||||
@@ -249,38 +299,38 @@ Object::MaybeDelete (void) const
|
||||
{
|
||||
// First, check if any of the attached
|
||||
// Object has a non-zero count.
|
||||
const Object *current = this;
|
||||
do {
|
||||
NS_ASSERT (current != 0);
|
||||
if (current->m_count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
current = current->m_next;
|
||||
} while (current != this);
|
||||
uint32_t n = m_aggregates->n;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
if (current->m_count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we are disposed.
|
||||
Object *tmp = const_cast<Object *> (this);
|
||||
const Object *end = this;
|
||||
do {
|
||||
NS_ASSERT (current != 0);
|
||||
Object *next = tmp->m_next;
|
||||
if (!tmp->m_disposed)
|
||||
{
|
||||
tmp->DoDispose ();
|
||||
}
|
||||
tmp = next;
|
||||
} while (tmp != end);
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
Object *current = m_aggregates->buffer[i];
|
||||
if (!current->m_disposed)
|
||||
{
|
||||
current->DoDispose ();
|
||||
}
|
||||
}
|
||||
|
||||
// all attached objects have a zero count so,
|
||||
// we can delete all attached objects.
|
||||
current = this;
|
||||
do {
|
||||
NS_ASSERT (current != 0);
|
||||
Object *next = current->m_next;
|
||||
delete current;
|
||||
current = next;
|
||||
} while (current != end);
|
||||
// we can delete them all.
|
||||
struct Aggregates *aggregates = m_aggregates;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
// There is a trick here: each time we call delete below,
|
||||
// the deleted object is removed from the aggregate buffer
|
||||
// in the destructor so, the index of the next element to
|
||||
// lookup is always zero
|
||||
Object *current = aggregates->buffer[0];
|
||||
delete current;
|
||||
}
|
||||
}
|
||||
} // namespace ns3
|
||||
|
||||
|
||||
@@ -85,9 +85,9 @@ public:
|
||||
Ptr<const Object> Next (void);
|
||||
private:
|
||||
friend class Object;
|
||||
AggregateIterator (Ptr<const Object> first);
|
||||
Ptr<const Object> m_first;
|
||||
Ptr<const Object> m_current;
|
||||
AggregateIterator (Ptr<const Object> object);
|
||||
Ptr<const Object> m_object;
|
||||
uint32_t m_current;
|
||||
};
|
||||
|
||||
Object ();
|
||||
@@ -215,6 +215,21 @@ private:
|
||||
friend class ObjectFactory;
|
||||
friend class AggregateIterator;
|
||||
|
||||
/**
|
||||
* This data structure uses a classic C-style trick to
|
||||
* hold an array of variable size without performing
|
||||
* two memory allocations: the declaration of the structure
|
||||
* declares a one-element array but when we allocate
|
||||
* memory for this struct, we effectively allocate a larger
|
||||
* chunk of memory than the struct to allow space for a larger
|
||||
* variable sized buffer whose size is indicated by the element
|
||||
* 'n'
|
||||
*/
|
||||
struct Aggregates {
|
||||
uint32_t n;
|
||||
Object *buffer[1];
|
||||
};
|
||||
|
||||
Ptr<Object> DoGetObject (TypeId tid) const;
|
||||
bool Check (void) const;
|
||||
bool CheckLoose (void) const;
|
||||
@@ -243,6 +258,8 @@ private:
|
||||
*/
|
||||
void Construct (const AttributeList &attributes);
|
||||
|
||||
void UpdateSortedArray (struct Aggregates *aggregates, uint32_t i) const;
|
||||
|
||||
/**
|
||||
* The reference count for this object. Each aggregate
|
||||
* has an individual reference count. When the global
|
||||
@@ -261,13 +278,19 @@ private:
|
||||
*/
|
||||
bool m_disposed;
|
||||
/**
|
||||
* A pointer to the next aggregate object. This is a circular
|
||||
* linked list of aggregated objects: the last one points
|
||||
* back to the first one. If an object is not aggregated to
|
||||
* any other object, the value of this field is equal to the
|
||||
* value of the 'this' pointer.
|
||||
* a pointer to an array of 'aggregates'. i.e., a pointer to
|
||||
* each object aggregated to this object is stored in this
|
||||
* array. The array is shared by all aggregated objects
|
||||
* so the size of the array is indirectly a reference count.
|
||||
*/
|
||||
Object *m_next;
|
||||
struct Aggregates * m_aggregates;
|
||||
/**
|
||||
* Indicates the number of times the object was accessed with a
|
||||
* call to GetObject. This integer is used to implement a
|
||||
* heuristic to sort the array of aggregates to put at the start
|
||||
* of the array the most-frequently accessed elements.
|
||||
*/
|
||||
uint32_t m_getObjectCount;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user