Anton Shepelev
2017-02-23 20:34:30 UTC
Hello, all
By way of excercise I have written a small dictio-
nary class and measured its performace in random ac-
cess, paying no attention to the insertion and dele-
tion of elements. My class does not implement ICol-
lection and has neither memory management nor error
handling.
May I ask you to review my code and indicate how
further to optimise random access, i.e. the indexer?
I have tried passing the the structs by value and by
reference, as well as operating bare array in-
dices -- only to observe no difference is perfor-
mance.
You can download the class, together with a small
test project, here:
https://drive.google.com/file/d/0B2hVk2IFUZDGcHFGbFpIOEp6OVk/view?usp=sharing
The principal source file I reproduce below:
using System;
using System.Collections.Generic;
namespace AntCols
{
struct TDictRec<TKey, TVal>
{ public TKey Key;
public TVal Value;
public AntListNode<ListEntry> ListElem; // position in the array
}
class ListEntry
{ public int Pos;
public ListEntry( int pos )
{ Pos = pos; }
}
enum ProbeRes { Done, More, Fail };
class DictEnumerator<TKey, TVal>: IEnumerator< KeyValuePair<TKey, TVal> >
{ AntDict<TKey, TVal> dict;
IEnumerator<ListEntry> list_enum;
public void Reset()
{ list_enum.Reset(); }
public bool MoveNext()
{ return list_enum.MoveNext(); }
public KeyValuePair<TKey, TVal> Current
{ get
{ int pos = list_enum.Current.Pos;
TKey key = dict.Data[ pos ].Key;
TVal val = dict.Data[ pos ].Value;
KeyValuePair<TKey, TVal> pr =
new KeyValuePair< TKey, TVal >( key, val );
return pr;
}
}
object System.Collections.IEnumerator.Current
{ get
{ return Current; }
}
public void Dispose()
{
// TODO
}
public DictEnumerator( AntDict< TKey, TVal > dict )
{ this.dict = dict;
this.list_enum = dict.List.GetEnumerator();
}
}
public class AntDict< TKey, TVal >: IEnumerable<KeyValuePair<TKey, TVal>>
{ // (stored index in Data)
internal AntList<ListEntry> List = new AntList<ListEntry>();
internal TDictRec<TKey, TVal>[] Data;
int Capacity;
int Count = 0;
public IEnumerator<KeyValuePair<TKey, TVal>> GetEnumerator()
{ return new DictEnumerator< TKey, TVal> ( this ); }
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
public TVal this[ TKey key ]
{ get
{ TVal value;
Find( key, out value );
return value;
//return Data[ Find( key ) ].Value;
}
}
public void Add( TKey key, TVal value )
{ if( Count > Capacity / 3 )
{ IncCapacity(); }
Count++;
int empty = FindEmpty( key );
if( empty == -1 )
{ throw new Exception( "No room" ); }
AntListNode<ListEntry> list_elem =
List.Append( new ListEntry( empty ) );
PutEntry( key, value, list_elem, ref Data[empty] );
}
public void Remove( TKey key )
{ TVal dummy;
Count--;
int pos = Find( key, out dummy );
List.Remove( Data[pos].ListElem );
Data[pos].ListElem = null; // TODO: Clear it properly
}
void PutEntry
( TKey key, TVal value, AntListNode<ListEntry> list_elem,
ref TDictRec<TKey, TVal> record
)
{ record.Key = key;
record.Value = value;
record.ListElem = list_elem;
}
delegate ProbeRes ProbeCrit
( ref TDictRec<TKey, TVal> rec, TKey compare );
ProbeCrit CritEqual, CritEmpty;
ProbeRes IsEmpty( ref TDictRec<TKey, TVal> rec, TKey compare )
{ if( rec.ListElem == null )
{ return ProbeRes.Done; }
else
{ return ProbeRes.More; }
}
ProbeRes IsEqual( ref TDictRec<TKey, TVal> rec, TKey compare )
{ if( IsEmpty( ref rec, compare ) == ProbeRes.Done )
{ return ProbeRes.Fail; }
if( compare.Equals( rec.Key ) )
{ return ProbeRes.Done; }
return ProbeRes.More;
}
int Find( TKey key, out TVal value )
{ return Probe( CritEqual, key, out value ); }
int FindEmpty( TKey key )
{ TVal dummy;
return Probe( CritEmpty, key, out dummy );
}
public AntDict()
{ Capacity = 1;
IncCapacity();
CritEqual = new ProbeCrit( IsEqual );
CritEmpty = new ProbeCrit( IsEmpty );
}
void NextIndex( ref int index, ref int iter )
{ iter++;
index += iter;
if( index >= Capacity )
{ index = index % Capacity; }
}
int ProbeTest0( ProbeCrit crit )
{ return 0;
}
int Probe( ProbeCrit crit, TKey key, out TVal value )
{ // HACK: how to use the entire hash value?
int hash = key.GetHashCode() & 0x7FFFFFFF % Capacity;
int pos = hash;
int iter = 0;
int res = -1;
TDictRec<TKey, TVal> rec;
while( true )
{ rec = Data[pos];
switch( crit( ref rec, key ) )
{ case ProbeRes.Fail: value = rec.Value; return res;
case ProbeRes.Done: value = rec.Value; return pos;
}
NextIndex( ref pos, ref iter );
}
}
void IncCapacity()
{ TDictRec<TKey, TVal>[] old_data = Data;
Capacity *= 2;
Data = new TDictRec<TKey, TVal>[ Capacity ];
if( old_data != null )
{ Resize( old_data );
}
}
void Resize( TDictRec<TKey, TVal>[] old_data )
{ int pos;
TDictRec<TKey, TVal> record;
foreach( ListEntry entry in List )
{ record = old_data[ entry.Pos ];
pos = FindEmpty( record.Key );
Data[pos] = record;
entry.Pos = pos;
}
}
}
}
By way of excercise I have written a small dictio-
nary class and measured its performace in random ac-
cess, paying no attention to the insertion and dele-
tion of elements. My class does not implement ICol-
lection and has neither memory management nor error
handling.
May I ask you to review my code and indicate how
further to optimise random access, i.e. the indexer?
I have tried passing the the structs by value and by
reference, as well as operating bare array in-
dices -- only to observe no difference is perfor-
mance.
You can download the class, together with a small
test project, here:
https://drive.google.com/file/d/0B2hVk2IFUZDGcHFGbFpIOEp6OVk/view?usp=sharing
The principal source file I reproduce below:
using System;
using System.Collections.Generic;
namespace AntCols
{
struct TDictRec<TKey, TVal>
{ public TKey Key;
public TVal Value;
public AntListNode<ListEntry> ListElem; // position in the array
}
class ListEntry
{ public int Pos;
public ListEntry( int pos )
{ Pos = pos; }
}
enum ProbeRes { Done, More, Fail };
class DictEnumerator<TKey, TVal>: IEnumerator< KeyValuePair<TKey, TVal> >
{ AntDict<TKey, TVal> dict;
IEnumerator<ListEntry> list_enum;
public void Reset()
{ list_enum.Reset(); }
public bool MoveNext()
{ return list_enum.MoveNext(); }
public KeyValuePair<TKey, TVal> Current
{ get
{ int pos = list_enum.Current.Pos;
TKey key = dict.Data[ pos ].Key;
TVal val = dict.Data[ pos ].Value;
KeyValuePair<TKey, TVal> pr =
new KeyValuePair< TKey, TVal >( key, val );
return pr;
}
}
object System.Collections.IEnumerator.Current
{ get
{ return Current; }
}
public void Dispose()
{
// TODO
}
public DictEnumerator( AntDict< TKey, TVal > dict )
{ this.dict = dict;
this.list_enum = dict.List.GetEnumerator();
}
}
public class AntDict< TKey, TVal >: IEnumerable<KeyValuePair<TKey, TVal>>
{ // (stored index in Data)
internal AntList<ListEntry> List = new AntList<ListEntry>();
internal TDictRec<TKey, TVal>[] Data;
int Capacity;
int Count = 0;
public IEnumerator<KeyValuePair<TKey, TVal>> GetEnumerator()
{ return new DictEnumerator< TKey, TVal> ( this ); }
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
public TVal this[ TKey key ]
{ get
{ TVal value;
Find( key, out value );
return value;
//return Data[ Find( key ) ].Value;
}
}
public void Add( TKey key, TVal value )
{ if( Count > Capacity / 3 )
{ IncCapacity(); }
Count++;
int empty = FindEmpty( key );
if( empty == -1 )
{ throw new Exception( "No room" ); }
AntListNode<ListEntry> list_elem =
List.Append( new ListEntry( empty ) );
PutEntry( key, value, list_elem, ref Data[empty] );
}
public void Remove( TKey key )
{ TVal dummy;
Count--;
int pos = Find( key, out dummy );
List.Remove( Data[pos].ListElem );
Data[pos].ListElem = null; // TODO: Clear it properly
}
void PutEntry
( TKey key, TVal value, AntListNode<ListEntry> list_elem,
ref TDictRec<TKey, TVal> record
)
{ record.Key = key;
record.Value = value;
record.ListElem = list_elem;
}
delegate ProbeRes ProbeCrit
( ref TDictRec<TKey, TVal> rec, TKey compare );
ProbeCrit CritEqual, CritEmpty;
ProbeRes IsEmpty( ref TDictRec<TKey, TVal> rec, TKey compare )
{ if( rec.ListElem == null )
{ return ProbeRes.Done; }
else
{ return ProbeRes.More; }
}
ProbeRes IsEqual( ref TDictRec<TKey, TVal> rec, TKey compare )
{ if( IsEmpty( ref rec, compare ) == ProbeRes.Done )
{ return ProbeRes.Fail; }
if( compare.Equals( rec.Key ) )
{ return ProbeRes.Done; }
return ProbeRes.More;
}
int Find( TKey key, out TVal value )
{ return Probe( CritEqual, key, out value ); }
int FindEmpty( TKey key )
{ TVal dummy;
return Probe( CritEmpty, key, out dummy );
}
public AntDict()
{ Capacity = 1;
IncCapacity();
CritEqual = new ProbeCrit( IsEqual );
CritEmpty = new ProbeCrit( IsEmpty );
}
void NextIndex( ref int index, ref int iter )
{ iter++;
index += iter;
if( index >= Capacity )
{ index = index % Capacity; }
}
int ProbeTest0( ProbeCrit crit )
{ return 0;
}
int Probe( ProbeCrit crit, TKey key, out TVal value )
{ // HACK: how to use the entire hash value?
int hash = key.GetHashCode() & 0x7FFFFFFF % Capacity;
int pos = hash;
int iter = 0;
int res = -1;
TDictRec<TKey, TVal> rec;
while( true )
{ rec = Data[pos];
switch( crit( ref rec, key ) )
{ case ProbeRes.Fail: value = rec.Value; return res;
case ProbeRes.Done: value = rec.Value; return pos;
}
NextIndex( ref pos, ref iter );
}
}
void IncCapacity()
{ TDictRec<TKey, TVal>[] old_data = Data;
Capacity *= 2;
Data = new TDictRec<TKey, TVal>[ Capacity ];
if( old_data != null )
{ Resize( old_data );
}
}
void Resize( TDictRec<TKey, TVal>[] old_data )
{ int pos;
TDictRec<TKey, TVal> record;
foreach( ListEntry entry in List )
{ record = old_data[ entry.Pos ];
pos = FindEmpty( record.Key );
Data[pos] = record;
entry.Pos = pos;
}
}
}
}
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]