Skip to content

Commit 513bf37

Browse files
committed
speed up FastCloneState
1 parent 37abc82 commit 513bf37

File tree

2 files changed

+101
-78
lines changed

2 files changed

+101
-78
lines changed

FastCloner/Code/FastCloneState.cs

Lines changed: 94 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
using System.Runtime.CompilerServices;
1+
namespace FastCloner.Code;
22

3-
namespace FastCloner.Code;
3+
using System.Runtime.CompilerServices;
44

5-
internal class FastCloneState
5+
internal sealed class FastCloneState
66
{
77
private MiniDictionary? loops;
88
private readonly object[] baseFromTo = new object[6];
99
private int idx;
1010

11+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1112
public object? GetKnownRef(object from)
1213
{
13-
// this is faster than call Dictionary from begin
14-
// also, small poco objects does not have a lot of references
15-
object[] baseFromTo = this.baseFromTo;
16-
if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3];
17-
if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4];
18-
if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5];
19-
20-
return loops?.FindEntry(from);
14+
return idx switch
15+
{
16+
1 when ReferenceEquals(from, baseFromTo[0]) => baseFromTo[3],
17+
2 when ReferenceEquals(from, baseFromTo[0]) => baseFromTo[3],
18+
2 when ReferenceEquals(from, baseFromTo[1]) => baseFromTo[4],
19+
3 when ReferenceEquals(from, baseFromTo[0]) => baseFromTo[3],
20+
3 when ReferenceEquals(from, baseFromTo[1]) => baseFromTo[4],
21+
3 when ReferenceEquals(from, baseFromTo[2]) => baseFromTo[5],
22+
_ => loops?.FindEntry(from)
23+
};
2124
}
2225

2326
public void AddKnownRef(object from, object to)
@@ -34,22 +37,22 @@ public void AddKnownRef(object from, object to)
3437
loops.Insert(from, to);
3538
}
3639

37-
private class MiniDictionary
40+
private sealed class MiniDictionary
3841
{
39-
private struct Entry
42+
private readonly struct Entry(int hashCode, int next, object key, object value)
4043
{
41-
public int HashCode;
42-
public int Next;
43-
public object Key;
44-
public object Value;
44+
public readonly int HashCode = hashCode;
45+
public readonly int Next = next;
46+
public readonly object Key = key;
47+
public readonly object Value = value;
4548
}
4649

4750
private int[]? buckets;
4851
private Entry[] entries;
4952
private int count;
53+
private const int DefaultCapacity = 5;
5054

51-
52-
public MiniDictionary() : this(5)
55+
public MiniDictionary() : this(DefaultCapacity)
5356
{
5457
}
5558

@@ -59,22 +62,29 @@ public MiniDictionary(int capacity)
5962
Initialize(capacity);
6063
}
6164

62-
public object FindEntry(object key)
65+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
66+
public object? FindEntry(object key)
6367
{
64-
if (buckets != null)
68+
if (buckets is null)
6569
{
66-
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
67-
Entry[] entries1 = entries;
68-
for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries1[i].Next)
69-
{
70-
if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key))
71-
return entries1[i].Value;
72-
}
70+
return null;
71+
}
72+
73+
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
74+
int bucketIndex = hashCode % buckets.Length;
75+
76+
Entry[] entriesLocal = entries;
77+
for (int i = buckets[bucketIndex]; i >= 0; i = entriesLocal[i].Next)
78+
{
79+
ref readonly Entry entry = ref entriesLocal[i];
80+
if (entry.HashCode == hashCode && ReferenceEquals(entry.Key, key))
81+
return entry.Value;
7382
}
7483

7584
return null;
7685
}
7786

87+
// Hash Table Primes, stabilizes to ~1.2x increase
7888
private static readonly int[] primes =
7989
[
8090
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
@@ -84,84 +94,89 @@ public object FindEntry(object key)
8494
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
8595
];
8696

97+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8798
private static int GetPrime(int min)
8899
{
89-
for (int i = 0; i < primes.Length; i++)
100+
int left = 0;
101+
int right = primes.Length - 1;
102+
103+
while (left <= right)
90104
{
91-
int prime = primes[i];
92-
if (prime >= min) return prime;
105+
int mid = left + (right - left) / 2;
106+
if (primes[mid] >= min)
107+
{
108+
right = mid - 1;
109+
}
110+
else
111+
{
112+
left = mid + 1;
113+
}
93114
}
115+
116+
return left < primes.Length ? primes[left] : GeneratePrime(min);
117+
}
94118

95-
//outside of our predefined table.
96-
//compute the hard way.
119+
private static int GeneratePrime(int min)
120+
{
97121
for (int i = min | 1; i < int.MaxValue; i += 2)
98122
{
99-
if (IsPrime(i) && (i - 1) % 101 != 0)
123+
if (IsPrime(i) && (i - 1) % 101 is not 0)
100124
return i;
101125
}
102-
103126
return min;
104127
}
105128

106129
private static bool IsPrime(int candidate)
107130
{
108-
if ((candidate & 1) != 0)
131+
if ((candidate & 1) is 0)
132+
return candidate is 2;
133+
134+
int limit = (int)Math.Sqrt(candidate);
135+
for (int divisor = 3; divisor <= limit; divisor += 2)
109136
{
110-
int limit = (int)Math.Sqrt(candidate);
111-
for (int divisor = 3; divisor <= limit; divisor += 2)
112-
{
113-
if ((candidate % divisor) == 0)
114-
return false;
115-
}
116-
117-
return true;
137+
if (candidate % divisor is 0)
138+
return false;
118139
}
119-
120-
return candidate == 2;
140+
return true;
121141
}
122142

123143
private static int ExpandPrime(int oldSize)
124144
{
125145
int newSize = 2 * oldSize;
126-
127146
if ((uint)newSize > 0x7FEFFFFD && 0x7FEFFFFD > oldSize)
128-
{
129147
return 0x7FEFFFFD;
130-
}
131-
132148
return GetPrime(newSize);
133149
}
134150

135151
private void Initialize(int size)
136152
{
137153
buckets = new int[size];
138-
for (int i = 0; i < buckets.Length; i++)
139-
buckets[i] = -1;
154+
Array.Fill(buckets, -1);
140155
entries = new Entry[size];
141156
}
142157

158+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
143159
public void Insert(object key, object value)
144160
{
145-
if (buckets == null) Initialize(0);
161+
if (buckets is null)
162+
Initialize(DefaultCapacity);
163+
146164
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
147-
int targetBucket = hashCode % buckets.Length;
148-
149-
Entry[] entries1 = entries;
165+
int targetBucket = hashCode % buckets!.Length;
150166

151-
if (count == entries1.Length)
167+
if (count == entries.Length)
152168
{
153169
Resize();
154-
entries1 = entries;
155170
targetBucket = hashCode % buckets.Length;
156171
}
157172

158-
int index = count;
159-
count++;
160-
161-
entries1[index].HashCode = hashCode;
162-
entries1[index].Next = buckets[targetBucket];
163-
entries1[index].Key = key;
164-
entries1[index].Value = value;
173+
int index = count++;
174+
entries[index] = new Entry(
175+
hashCode,
176+
buckets[targetBucket],
177+
key,
178+
value
179+
);
165180
buckets[targetBucket] = index;
166181
}
167182

@@ -170,23 +185,30 @@ public void Insert(object key, object value)
170185
private void Resize(int newSize)
171186
{
172187
int[] newBuckets = new int[newSize];
173-
for (int i = 0; i < newBuckets.Length; i++)
174-
newBuckets[i] = -1;
188+
Array.Fill(newBuckets, -1);
189+
175190
Entry[] newEntries = new Entry[newSize];
176191
Array.Copy(entries, 0, newEntries, 0, count);
177192

178193
for (int i = 0; i < count; i++)
179194
{
180-
if (newEntries[i].HashCode >= 0)
195+
if (newEntries[i].HashCode < 0)
181196
{
182-
int bucket = newEntries[i].HashCode % newSize;
183-
newEntries[i].Next = newBuckets[bucket];
184-
newBuckets[bucket] = i;
197+
continue;
185198
}
199+
200+
int bucket = newEntries[i].HashCode % newSize;
201+
newEntries[i] = new Entry(
202+
newEntries[i].HashCode,
203+
newBuckets[bucket],
204+
newEntries[i].Key,
205+
newEntries[i].Value
206+
);
207+
newBuckets[bucket] = i;
186208
}
187209

188210
buckets = newBuckets;
189211
entries = newEntries;
190212
}
191213
}
192-
}
214+
}

FastCloner/Code/FastClonerGenerator.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,22 @@ internal static class FastClonerGenerator
5252

5353
internal static object? CloneClassInternal(object? obj, FastCloneState state)
5454
{
55-
if (obj == null)
55+
if (obj is null)
56+
{
5657
return null;
58+
}
5759

5860
Func<object, FastCloneState, object>? cloner = (Func<object, FastCloneState, object>)FastClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true));
5961

6062
// safe object
61-
if (cloner == null)
63+
if (cloner is null)
64+
{
6265
return obj;
66+
}
6367

6468
// loop
6569
object? knownRef = state.GetKnownRef(obj);
66-
if (knownRef != null)
67-
return knownRef;
68-
69-
return cloner(obj, state);
70+
return knownRef ?? cloner(obj, state);
7071
}
7172

7273
internal static T CloneStructInternal<T>(T obj, FastCloneState state) // where T : struct

0 commit comments

Comments
 (0)