Fast attribute class?

Started by
1 comment, last by 3DModelerMan 12 years ago
I have an attribute class that I use for saving and loading attributes to file. It's a very convenient way to quickly write out a whole bunch of data to a binary stream, and I can use it to edit fields on objects too. Kind of like the Irrlicht engine uses (if you've ever seen their system). I'm trying to optimize this class so that I can search for values fast, and also add and remove values fast. Here's my class:


class CAttributes
{
public:
///@brief This and all the following functions take a data type, and save it to the attribute file.
///@brief name The name of the attribute to set.
void setInt(const std::string& name, int i);
void setFloat(const std::string& name, float f);
void setString(const std::string& name, const std::string& str);
void setVector(const std::string& name, const Vector3& vec);
void setColor(const std::string& name, const Color& col);

///@brief This and all the following functions access the data that was loaded from the attribute file.
///@param name The name of the attribute to search for.
///@return The attribute value, or 0 if it wasn't found.
int getInt(const std::string& name);
///@return The attribute value, or 0.0 if it wasn't found.
float getFloat(const std::string& name);
///@return The attribute value, or "" if it wasn't found.
std::string getString(const std::string& name);
///@return The attribute value, or (0,0,0) if it wasn't found.
Vector3 getVector(const std::string& name);
///@return The attribute value, or (1.0,1.0,1.0,1.0) if the attribute wasn't found.
Color getColor(const std::string& name);
///@brief Deletes all the attributes contained in this object.
void clear();
///@brief Serializes this set of attributes to a file.
///@param out The file writer to serialize into.
void serialize(CFileWriter& out);
///@brief Deserializes this set of attributes from a stream.
///Attributes are deserialized in binary mode, so any stream that is passed to this method should be binary too.
///@param in The stream to deserialize from.
void deserialize(CFileReader& in);
///@brief This and all the following functions return references to the attribute maps. This allows access
///so that you can pull up file dialogs when creating components if a property has "File" in the name.
std::map<std::string, int>& getInts(){return m_intMap;}
std::map<std::string, float>& getFloats(){return m_floatMap;}
std::map<std::string, std::string>& getStrings(){return m_stringMap;}
std::map<std::string, Vector3>& getVectors(){return m_vecMap;}
std::map<std::string, Color>& getColors(){return m_colMap;}
private:
std::map<std::string, int> m_intMap;
std::map<std::string, float> m_floatMap;
std::map<std::string, std::string> m_stringMap;
std::map<std::string, Vector3> m_vecMap;
std::map<std::string, Color> m_colMap;
};


As you can see it just uses a whole bunch of std::map data containers to store the data of different types. Any suggestions for a faster implementation? One reason I want to speed it up is for my event system. Attributes would be a great way to send event data while only requiring one class.
Advertisement
If your Attributes objects only ever contain a few attributes, then arrays/vectors will be more efficient than maps.

You should never be using strings like that, you should internalize your strings into unique integer values, and use those integers for comparison.

To optimise this, I'd add a hefty dose of DoD, and make Attributes into a blob of bytes, beginning with a header, then an array of fixed-size attributes (name/type/value-tuples), and then an array of bytes for any large values (>32 bits) which didn't fit in the fixed-size array.
To fetch an attribute from the blob, just iterate the array of attributes until you find a name match, then call [font=courier new,courier,monospace]Value()[/font] to cast the 32-bit value, or fetch the value from the large-value blob. N.B. I'm assuming you only need POD (memcpy-safe) attributes.
Off the top of my head (might not compile or work), something like:struct Attribute // only valid to be used inside AttributeBlob instances
{
u32 name;
u32 type;//EType
u32 value;//either holds the value (if fits in 32 bits) or holds an offset to the value
template<class T> T& Value()
{
assert( GetType<T>() == type );
if( sizeof(T) <= sizeof(u32) )
return *(T*)&value;
else
return *(T*)(((u8*)&value)+value);
}
};

struct AttributeBlob // a collection of attributes
{
u32 sizeInBytes;
u32 numAttributes;
Attribute* First() { return (Attribute*)(this+1); }
AttributeBlob* Clone()
{
AttributeBlob* result = (AttributeBlob*)malloc( sizeInBytes );
memcpy( result, this, sizeInBytes );
}
void Delete()
{
free(this);
}
};

enum EType {...};//todo - basic type information system
template<class T> EType GetType<T>() { return ...; }
uint SizeOf( EType ) { return ...; }

class AttribWriter // temporary object used to create AttributeBlob instances
{
public:
void Begin()
{
attribs.resize(0);
values.resize(0);
}
template<class T> void Write(const char* name, const T& value)
{
Attribute a = { Internalize(name), GetType<T>() };
if( sizeof(T) <= sizeof(u32) )
a.value = *(u32*)&value;
else
{
a.value = values.size();
values.resize(a.value + sizeof(T));
memcpy( &values[a.value], &value, sizeof(T));
}
attribs.push_back(a);
}
AttributeBlob* End()
{ // header, followed by attrib array, follow by blob of large values
u32 size = sizeof(AttributeBlob) + sizeof(Attribute)*attribs.size() + values.size();
AttributeBlob* result = (AttributeBlob*)malloc( size );
result->sizeInBytes = size;
result->numAttributes = attribs.size();
Attribute* attrib = result->First();
u8* valueBlob = (u8*)attrib+result->numAttributes;
for( int i=0, end=result->numAttributes; i!=end; ++i )
{
attrib = attribs;
uint sizeOfValue = SizeOf(attribs.type);
if( sizeOfValue > sizeof(u32) )//large values contain an offset into the values vector
{
u32* jumpFrom = &attrib.value;
u8* jumpTo = valueBlob;//allocate some space in the large values blob
valueBlob += sizeOfValue;
memcpy( jumpTo, &values[attrib.value],sizeOfValue );//copy value from vector into the result's large values blob
*jumpFrom = jumpTo - (u8*)jumpFrom;//write offset from the attribute to the blob value
}
}
assert( valueBlob == ((u8*)result) + size );//should've written into the entire size we allocated
}
private:
std::vector<Attribute> attribs;
std::vector<u8> values;
};
That's a good idea. I think I'll try it. Most of my data will fit into 32 bits too.

This topic is closed to new replies.

Advertisement