Skip to content

Commit d9f0e4b

Browse files
committed
TinyDictionary
- doubling buckets width - add a hash finisher before masking
1 parent cad97a4 commit d9f0e4b

File tree

1 file changed

+52
-14
lines changed

1 file changed

+52
-14
lines changed

lib/Runtime/Types/TypePath.h

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Js
88
{
99
class TinyDictionary
1010
{
11-
static const int PowerOf2_BUCKETS = 8;
11+
static const int PowerOf2_BUCKETS = 16;
1212
static const int BUCKETS_DWORDS = PowerOf2_BUCKETS / sizeof(DWORD);
1313
static const byte NIL = 0xff;
1414

@@ -19,37 +19,72 @@ namespace Js
1919
TinyDictionary()
2020
{
2121
CompileAssert(BUCKETS_DWORDS * sizeof(DWORD) == PowerOf2_BUCKETS);
22-
CompileAssert(BUCKETS_DWORDS == 2);
22+
CompileAssert(BUCKETS_DWORDS == 4);
2323
DWORD* init = bucketsData;
24-
init[0] = init[1] = 0xffffffff;
24+
init[0] = init[1] = init[2] = init[3] =0xffffffff;
25+
}
26+
27+
uint32 ReduceKeyToIndex(PropertyId key)
28+
{
29+
// we use 4-bit bucket index
30+
// sometimes we have keys that differ in higher bits, so smudge the bits down a little
31+
// to reduce the possibility of collisions
32+
key ^= (uint)key >> 15;
33+
key ^= (uint)key >> 7;
34+
return key & (PowerOf2_BUCKETS - 1);
2535
}
2636

2737
void Add(PropertyId key, byte value)
2838
{
39+
Assert(value < 128);
40+
2941
byte* buckets = reinterpret_cast<byte*>(bucketsData);
30-
uint32 bucketIndex = key & (PowerOf2_BUCKETS - 1);
42+
uint32 bucketIndex = ReduceKeyToIndex(key);
3143

3244
byte i = buckets[bucketIndex];
33-
buckets[bucketIndex] = value;
34-
next[value] = i;
45+
if (i == NIL)
46+
{
47+
// set the highest bit to mark the value as the last in the chain
48+
buckets[bucketIndex] = value | 128;
49+
}
50+
else
51+
{
52+
buckets[bucketIndex] = value;
53+
next[value & 127] = i;
54+
}
3555
}
3656

3757
// Template shared with diagnostics
3858
template <class Data>
3959
inline bool TryGetValue(PropertyId key, PropertyIndex* index, const Data& data)
4060
{
4161
byte* buckets = reinterpret_cast<byte*>(bucketsData);
42-
uint32 bucketIndex = key & (PowerOf2_BUCKETS - 1);
62+
uint32 bucketIndex = ReduceKeyToIndex(key);
4363

44-
for (byte i = buckets[bucketIndex] ; i != NIL ; i = next[i])
64+
byte i = buckets[bucketIndex];
65+
if (i != NIL)
4566
{
46-
if (data[i]->GetPropertyId()== key)
47-
{
48-
*index = i;
49-
return true;
67+
for(;;)
68+
{
69+
byte idx = i & 127; // strip the sentinel bit
70+
71+
if (data[idx]->GetPropertyId() == key)
72+
{
73+
*index = idx;
74+
return true;
75+
}
76+
77+
if (i & 128)
78+
{
79+
// this was the last value in the chain
80+
break;
81+
}
82+
83+
Assert(idx != (next[idx] & 127));
84+
i = next[idx];
5085
}
51-
Assert(i != next[i]);
5286
}
87+
5388
return false;
5489
}
5590
};
@@ -75,7 +110,10 @@ namespace Js
75110
#define TYPE_PATH_ALLOC_GRANULARITY_GAP 3
76111
#endif
77112
#endif
78-
// Although we can allocate 2 more, this will put struct Data into another bucket. Just waste some slot in that case for 32-bit
113+
// Although we can allocate 2 more,
114+
// TinyDictionary can hold only up to 128 items (see TinyDictionary::Add),
115+
// Besides 128+ would put struct Data into another bucket.
116+
// Just waste some slot in that case for 32-bit
79117
static const uint MaxPathTypeHandlerLength = 128;
80118
static const uint InitialTypePathSize = 16 + TYPE_PATH_ALLOC_GRANULARITY_GAP;
81119

0 commit comments

Comments
 (0)