-
Notifications
You must be signed in to change notification settings - Fork 1
/
Types.cs
113 lines (100 loc) · 4.94 KB
/
Types.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SimdDictionary {
public partial class SimdDictionary<K, V>
where K : notnull
{
public const int InitialCapacity = 0,
// User-specified capacity values will be increased by this percentage in order
// to maintain an ideal load factor. FIXME: 120 isn't right
OversizePercentage = 120,
BucketSizeI = 14,
CountSlot = 14,
CascadeSlot = 15;
internal struct Pair {
public K Key;
public V Value;
}
// This size must match or exceed BucketSizeI
[InlineArray(14)]
internal struct InlinePairArray {
public Pair Pair0;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
internal struct Bucket {
public Vector128<byte> Suffixes;
public InlinePairArray Pairs;
// For 8-byte keys + values this makes a bucket 256 bytes, changing the native code for the lookup
// buckets[index] from an imul to a shift
// public Vector128<byte> Padding;
public ref byte Count {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref Unsafe.AddByteOffset(ref Unsafe.As<Vector128<byte>, byte>(ref Unsafe.AsRef(in Suffixes)), CountSlot);
}
public ref byte CascadeCount {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref Unsafe.AddByteOffset(ref Unsafe.As<Vector128<byte>, byte>(ref Unsafe.AsRef(in Suffixes)), CascadeSlot);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte GetSlot (int index) {
Debug.Assert(index < Vector128<byte>.Count);
// the extract-lane opcode this generates is slower than doing a byte load from memory,
// even if we already have the bucket in a register. Not sure why, but my guess based on agner's
// instruction tables is that it's because lane extract generates more uops than a byte move.
// the two operations have the same latency on icelake, and the byte move's latency is lower on zen4
// return self[index];
// index &= 15;
return Unsafe.AddByteOffset(ref Unsafe.As<Vector128<byte>, byte>(ref Unsafe.AsRef(in Suffixes)), index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetSlot (nuint index, byte value) {
Debug.Assert(index < (nuint)Vector128<byte>.Count);
// index &= 15;
Unsafe.AddByteOffset(ref Unsafe.As<Vector128<byte>, byte>(ref Suffixes), index) = value;
}
}
public enum InsertMode {
// Fail the insertion if a matching key is found
EnsureUnique,
// Overwrite the value if a matching key is found
OverwriteValue,
// Don't scan for existing matches before inserting into the bucket. This is only
// safe to do when copying an existing dictionary or rehashing an existing dictionary
Rehashing
}
public enum InsertResult {
// The specified key did not exist in the dictionary, and a key/value pair was inserted
OkAddedNew,
// The specified key was found in the dictionary and we overwrote the value
OkOverwroteExisting,
// The dictionary is full and needs to be grown before you can perform an insert
NeedToGrow,
// The specified key already exists in the dictionary, so nothing was done
KeyAlreadyPresent,
// The dictionary is clearly corrupted
CorruptedInternalState,
}
// We have separate implementations of FindKeyInBucket that get used depending on whether we have a null
// comparer for a valuetype, where we can rely on ryujit to inline EqualityComparer<K>.Default
internal interface IKeySearcher {
static abstract ref Pair FindKeyInBucket (
// We have to use UnscopedRef to allow lazy initialization
[UnscopedRef] ref Bucket bucket, int startIndexInBucket, int bucketCount,
IEqualityComparer<K>? comparer, K needle, out int matchIndexInBucket
);
static abstract uint GetHashCode (IEqualityComparer<K>? comparer, K key);
}
// Used to encapsulate operations that enumerate all the buckets synchronously (i.e. CopyTo)
internal interface IBucketCallback {
// Return false to stop iteration
abstract bool Bucket (ref Bucket bucket);
}
}
}