Hold C4Strings in a hashtable instead of a linked list

I didn't measure wether this helps performance, but I plan to use the
generic hashtable for function lists and object fields, too.
stable-5.2
Günther Brammer 2009-04-01 23:36:58 +02:00
parent 1fe837cc34
commit 6274cf7a5e
6 changed files with 170 additions and 109 deletions

View File

@ -24,11 +24,15 @@ class C4StringTable;
class C4String
{
C4String(C4StringTable *pTable);
C4String(StdStrBuf strString, C4StringTable *pTable);
explicit C4String(StdStrBuf strString);
explicit C4String(const char *strString);
StdCopyStrBuf Data; // string data
int iRefCnt; // reference count on string (by C4Value)
friend class C4StringTable;
public:
virtual ~C4String();
~C4String();
// increment/decrement reference count on this string
void IncRef();
@ -36,38 +40,139 @@ public:
const char * GetCStr() const { return Data.getData(); }
StdStrBuf GetData() const { return Data.getRef(); }
StdCopyStrBuf Data; // string data
int iRefCnt; // reference count on string (by C4Value)
bool Hold; // string stays hold when RefCnt reaches 0 (for in-script strings)
int iEnumID;
C4String *Next, *Prev; // double-linked list
C4StringTable *pTable; // owning table
void Reg(C4StringTable *pTable);
void UnReg();
unsigned int Hash;
};
template<typename T> class C4Set
{
unsigned int Capacity;
unsigned int Size;
T * Table;
void AddInternal(T e)
{
unsigned int h = Hash(e);
T * p = &Table[h % Capacity];
while (*p && *p != e)
{
p = &Table[++h % Capacity];
}
*p = e;
}
public:
template<typename H> static unsigned int Hash(H);
template<typename H> static bool Equals(T, H);
static bool Equals(T a, T b) { return a == b; }
// FIXME: Profile for initial size
C4Set(): Capacity(16), Size(0), Table(new T[Capacity])
{
Clear();
}
~C4Set()
{
delete[] Table;
}
void Clear()
{
for (unsigned int i = 0; i < Capacity; ++i)
Table[i] = 0;
}
template<typename H> T Get(H e)
{
unsigned int h = Hash(e);
T r = Table[h % Capacity];
while (r && !Equals(r, e))
{
r = Table[++h % Capacity];
}
return r;
}
unsigned int GetSize() { return Size; }
void Add(T e)
{
// FIXME: Profile for load factor
if (Size > Capacity / 2)
{
unsigned int OCapacity = Capacity;
Capacity *= 2;
T * OTable = Table;
Table = new T[Capacity];
Clear();
//memset(Table, 0, sizeof (T *) * NCapacity);
for (unsigned int i = 0; i < OCapacity; ++i)
{
if (OTable[i])
AddInternal(OTable[i]);
}
delete [] OTable;
}
AddInternal(e);
++Size;
}
template<typename H> void Remove(H e)
{
unsigned int h = Hash(e);
T * r = &Table[h % Capacity];
while (*r && !Equals(*r, e))
{
r = &Table[++h % Capacity];
}
assert(*r);
*r = 0;
--Size;
// Move entries which might have collided with e
while (*(r = &Table[++h % Capacity]))
{
T m = *r;
*r = 0;
AddInternal(m);
}
}
T const * First() const { return Next(Table - 1); }
T const * Next(T const * p) const
{
while (++p != &Table[Capacity])
{
if (*p) return p;
}
return 0;
}
};
template<> template<>
inline unsigned int C4Set<C4String *>::Hash<const C4String *>(const C4String * e)
{
return e->Hash;
}
template<> template<>
inline unsigned int C4Set<C4String *>::Hash<C4String *>(C4String * e)
{
return e->Hash;
}
// There is only one Stringtable in Game.ScriptEngine
class C4StringTable
{
public:
C4StringTable();
friend class C4AulScriptEngine;
public:
virtual ~C4StringTable();
void Clear();
C4String *RegString(StdStrBuf String);
C4String *RegString(const char * s) { return RegString(StdStrBuf(s)); }
// Find existing C4String
C4String *FindString(const char *strString);
// Check wether the pointer is a C4String
C4String *FindString(C4String *pString);
// Get a string from the Strings.txt
C4String *FindString(int iEnumID);
bool Load(C4Group& ParentGroup);
C4String *First, *Last; // string list
C4Set<C4String *> Set;
std::vector<C4String *> Stringstxt;
};
#endif

View File

@ -795,7 +795,6 @@ C4AulTokenType C4AulParseState::GetNextToken(char *pToken, long int *pInt, HoldS
// reg string (if not already done so)
C4String *pString;
pString = a->Engine->Strings.RegString(StdStrBuf(StrBuff,static_cast<long>(pStrPos - StrBuff)));
if (HoldStrings == Hold) pString->Hold = 1;
// return pointer on string object
*pInt = (long) pString;
return ATT_STRING;

View File

@ -2280,7 +2280,7 @@ void C4Command::CompileFunc(StdCompiler *pComp)
if(pComp->isDecompiler())
{
if(Text)
TextBuf.Ref(Text->Data);
TextBuf.Ref(Text->GetData());
else
TextBuf.Ref("0");
}

View File

@ -2961,7 +2961,7 @@ static const int32_t CSPF_FixedAttributes = 1<<0,
static bool FnCreateScriptPlayer(C4AulContext *cthr, C4String *szName, long dwColor, long idTeam, long dwFlags, C4ID idExtra)
{
// safety
if (!szName || !szName->Data.getLength()) return false;
if (!szName || !szName->GetData().getLength()) return false;
// this script command puts a new script player info into the list
// the actual join will be delayed and synchronized via queue
// processed by control host only - clients/replay/etc. will perform the join via queue
@ -3910,7 +3910,7 @@ static C4Value FnGetLength(C4AulContext *cthr, C4Value *pPars)
return C4VInt(pArray->GetSize());
C4String * pStr = pPars->getStr();
if (pStr)
return C4VInt(pStr->Data.getLength());
return C4VInt(pStr->GetData().getLength());
throw new C4AulExecError(cthr->Obj, "func \"GetLength\" par 0 cannot be converted to string or array");
}
@ -6295,7 +6295,7 @@ static bool FnPauseGame(C4AulContext *ctx, bool fToggle)
static bool FnSetNextMission(C4AulContext *ctx, C4String *szNextMission, C4String *szNextMissionText, C4String *szNextMissionDesc)
{
if (!szNextMission || !szNextMission->Data.getLength())
if (!szNextMission || !szNextMission->GetData().getLength())
{
// param empty: clear next mission
Game.NextMission.Clear();
@ -6304,10 +6304,10 @@ static bool FnSetNextMission(C4AulContext *ctx, C4String *szNextMission, C4Strin
else
{
// set next mission, button and button desc if given
Game.NextMission.Copy(szNextMission->Data);
Game.NextMission.Copy(szNextMission->GetData());
if (szNextMissionText && szNextMissionText->GetCStr())
{
Game.NextMissionText.Copy(szNextMissionText->Data);
Game.NextMissionText.Copy(szNextMissionText->GetData());
}
else
{
@ -6315,7 +6315,7 @@ static bool FnSetNextMission(C4AulContext *ctx, C4String *szNextMission, C4Strin
}
if (szNextMissionDesc && szNextMissionDesc->GetCStr())
{
Game.NextMissionDesc.Copy(szNextMissionDesc->Data);
Game.NextMissionDesc.Copy(szNextMissionDesc->GetData());
}
else
{

View File

@ -23,31 +23,43 @@
#include <C4Group.h>
#include <C4Components.h>
#include <C4Aul.h>
#include <C4Game.h>
#endif
// *** C4Set
template<> template<>
unsigned int C4Set<C4String *>::Hash<const char *>(const char * s)
{
// Fowler/Noll/Vo hash
unsigned int h = 2166136261u;
while (*s)
h = (h ^ *(s++)) * 16777619;
return h;
}
template<> template<>
bool C4Set<C4String *>::Equals<const char *>(C4String * a, const char * b)
{
return a->GetData() == b;
}
// *** C4String
C4String::C4String(C4StringTable *pnTable)
: Data(NULL), iRefCnt(0), Hold(false), iEnumID(-1), pTable(NULL)
{
// reg
Reg(pnTable);
}
C4String::C4String(StdStrBuf strString, C4StringTable *pnTable)
: iRefCnt(0), Hold(false), iEnumID(-1), pTable(NULL)
C4String::C4String(StdStrBuf strString)
: iRefCnt(0)
{
// take string
Data.Take(strString);
Hash = C4Set<C4String*>::Hash(Data.getData());
// reg
Reg(pnTable);
Game.ScriptEngine.Strings.Set.Add(this);
}
C4String::~C4String()
{
// unreg
iRefCnt = 1;
if(pTable) UnReg();
Game.ScriptEngine.Strings.Set.Remove(this);
}
void C4String::IncRef()
@ -58,46 +70,6 @@ void C4String::IncRef()
void C4String::DecRef()
{
--iRefCnt;
// delete if ref cnt is 0 and the Hold-Flag isn't set
if(iRefCnt <= 0 && !Hold)
delete this;
}
void C4String::Reg(C4StringTable *pnTable)
{
if(pTable) UnReg();
// add string to tail of table
Prev = pnTable->Last;
Next = NULL;
if(Prev)
Prev->Next = this;
else
pnTable->First = this;
pnTable->Last = this;
pTable = pnTable;
}
void C4String::UnReg()
{
if(!pTable) return;
if(Next)
Next->Prev = Prev;
else
pTable->Last = Prev;
if(Prev)
Prev->Next = Next;
else
pTable->First = Next;
pTable = NULL;
// delete hold flag if table is lost and check for delete
Hold = false;
if(iRefCnt <= 0)
delete this;
}
@ -105,33 +77,20 @@ void C4String::UnReg()
// *** C4StringTable
C4StringTable::C4StringTable()
: First(NULL), Last(NULL)
{
}
C4StringTable::~C4StringTable()
{
// unreg all remaining strings
// (hold strings will delete themselves)
while(First) First->UnReg();
Clear();
assert(!Set.GetSize());
}
void C4StringTable::Clear()
{
bool bContinue;
do
{
bContinue = false;
// find string to delete / unreg
for(C4String *pAct = First; pAct; pAct = pAct->Next)
if(pAct->Hold)
{
pAct->UnReg();
bContinue = true;
break;
}
}
while(bContinue);
for (unsigned int i = 0; i < Stringstxt.size(); ++i)
Stringstxt[i]->DecRef();
Stringstxt.clear();
}
C4String *C4StringTable::RegString(StdStrBuf String)
@ -140,35 +99,31 @@ C4String *C4StringTable::RegString(StdStrBuf String)
if (s)
return s;
else
return new C4String(String, this);
return new C4String(String);
}
C4String *C4StringTable::FindString(const char *strString)
{
for(C4String *pAct = First; pAct; pAct = pAct->Next)
if(SEqual(pAct->Data.getData(), strString))
return pAct;
return NULL;
return Set.Get(strString);
}
C4String *C4StringTable::FindString(C4String *pString)
{
for(C4String *pAct = First; pAct; pAct = pAct->Next)
if(pAct == pString)
return pAct;
return NULL;
for (C4String * const * i = Set.First(); i; i = Set.Next(i))
if (*i == pString)
return pString;
}
C4String *C4StringTable::FindString(int iEnumID)
{
for(C4String *pAct = First; pAct; pAct = pAct->Next)
if(pAct->iEnumID == iEnumID)
return pAct;
if (iEnumID >= 0 && iEnumID < int(Stringstxt.size()))
return Stringstxt[iEnumID];
return NULL;
}
bool C4StringTable::Load(C4Group& ParentGroup)
{
Clear();
// read data
char *pData;
if(!ParentGroup.LoadEntry(C4CFN_Strings, &pData, NULL, 1))
@ -180,9 +135,9 @@ bool C4StringTable::Load(C4Group& ParentGroup)
SReplaceChar(strBuf, 0x0D, 0x00);
// add string to list
C4String *pnString;
if(!(pnString = FindString(strBuf)))
pnString = RegString(StdStrBuf(strBuf));
pnString->iEnumID = i;
pnString = RegString(StdStrBuf(strBuf));
pnString->IncRef();
Stringstxt.push_back(pnString);
}
// delete data
delete[] pData;

View File

@ -261,6 +261,7 @@ C4V_Type C4Value::GuessType()
// object?
if (Game.Objects.ObjectNumber(Data.Obj))
{
assert(false);
Type = C4V_C4Object;
// With the type now known, the destructor will clean up the reference
// which only works if the reference is added first
@ -271,6 +272,7 @@ C4V_Type C4Value::GuessType()
// string?
if (Game.ScriptEngine.Strings.FindString(Data.Str))
{
assert(false);
Type = C4V_String;
// see above
AddDataRef();
@ -797,7 +799,7 @@ bool C4Value::operator == (const C4Value& Value2) const
case C4V_C4Object:
return Data == Value2.Data && Type == Value2.Type;
case C4V_String:
return Type == Value2.Type && Data.Str->Data == Value2.Data.Str->Data;
return Type == Value2.Type && Data.Str == Value2.Data.Str;
case C4V_Array:
return Type == Value2.Type && *(Data.Array) == *(Value2.Data.Array);
default: