Skip to content

Commit 2ea94a4

Browse files
authored
Avoid Hashtable-related allocations in AccessorTable (#6500)
Use a strongly-typed Dictionary instead. - The key is a struct. Every time an item is added to the table or a lookup into the table is performed, the key is getting boxed. Using Dictionary avoids all this boxing. - The struct key lacks a strongly-typed Equals. Every comparison results in a boxing. Gave the key a strongly-typed Equals. - Every cleanup operation is allocating an enumerator. Dictionary has a struct-based enumerator. - Every cleanup operation is allocating an array to store the items to be removed (even if there aren't any to be removed). Dictionary supports removal during enumeration, so this can be removed entirely.
1 parent 5717fc2 commit 2ea94a4

File tree

2 files changed

+33
-51
lines changed

2 files changed

+33
-51
lines changed

src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/AccessorTable.cs

Lines changed: 32 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ property. We cache the result of this discovery process in the
1616

1717
using System;
1818
using System.Collections;
19+
using System.Collections.Generic;
1920
using System.ComponentModel; // IBindingList
21+
using System.Diagnostics;
2022
using System.Reflection; // TypeDescriptor
2123
using System.Windows; // SR
2224
using System.Windows.Threading; // Dispatcher
@@ -39,10 +41,10 @@ internal AccessorInfo(object accessor, Type propertyType, object[] args)
3941

4042
internal int Generation { get { return _generation; } set { _generation = value; } }
4143

42-
object _accessor; // DP, PD, or PI
43-
Type _propertyType; // type of the property
44-
object[] _args; // args for indexed property
45-
int _generation; // used for discarding aged entries
44+
private readonly object _accessor; // DP, PD, or PI
45+
private readonly Type _propertyType; // type of the property
46+
private readonly object[] _args; // args for indexed property
47+
private int _generation; // used for discarding aged entries
4648
}
4749

4850

@@ -60,9 +62,7 @@ internal AccessorTable()
6062
if (type == null || name == null)
6163
return null;
6264

63-
AccessorInfo info = (AccessorInfo)_table[new AccessorTableKey(sourceValueType, type, name)];
64-
65-
if (info != null)
65+
if (_table.TryGetValue(new AccessorTableKey(sourceValueType, type, name), out AccessorInfo info))
6666
{
6767
#if DEBUG
6868
// record the age of cache hits
@@ -111,41 +111,35 @@ private void RequestCleanup()
111111
// run a cleanup pass
112112
private object CleanupOperation(object arg)
113113
{
114-
// find entries that are sufficiently old
115-
object[] keysToRemove = new object[_table.Count];
116-
int n = 0;
117-
IDictionaryEnumerator ide = _table.GetEnumerator();
118-
while (ide.MoveNext())
114+
#if DEBUG
115+
int originalCount = _table.Count;
116+
#endif
117+
118+
// Remove entries that are sufficiently old
119+
foreach (KeyValuePair<AccessorTableKey, AccessorInfo> entry in _table)
119120
{
120-
AccessorInfo info = (AccessorInfo)ide.Value;
121-
int age = _generation - info.Generation;
121+
int age = _generation - entry.Value.Generation;
122122
if (age >= AgeLimit)
123123
{
124-
keysToRemove[n++] = ide.Key;
124+
_table.Remove(entry.Key);
125125
}
126126
}
127127

128128
#if DEBUG
129129
if (_traceSize)
130130
{
131-
Console.WriteLine("After generation {0}, removing {1} of {2} entries from AccessorTable, new count is {3}",
132-
_generation, n, _table.Count, _table.Count - n);
131+
Console.WriteLine($"After generation {_generation}, removed {originalCount - _table.Count} of {originalCount} entries from AccessorTable, new count is {_table.Count}");
133132
}
134133
#endif
135134

136-
// remove those entries
137-
for (int i = 0; i < n; ++i)
138-
{
139-
_table.Remove(keysToRemove[i]);
140-
}
141-
142135
++_generation;
143136

144137
_cleanupRequested = false;
145138
return null;
146139
}
147140

148141
// print data about how the cache behaved
142+
[Conditional("DEBUG")]
149143
internal void PrintStats()
150144
{
151145
#if DEBUG
@@ -179,7 +173,7 @@ internal bool TraceSize
179173

180174
private const int AgeLimit = 10; // entries older than this get removed.
181175

182-
private Hashtable _table = new Hashtable();
176+
private readonly Dictionary<AccessorTableKey, AccessorInfo> _table = new Dictionary<AccessorTableKey, AccessorInfo>();
183177
private int _generation;
184178
private bool _cleanupRequested;
185179
bool _traceSize;
@@ -188,46 +182,33 @@ internal bool TraceSize
188182
private int _hits, _misses;
189183
#endif
190184

191-
private struct AccessorTableKey
185+
private readonly struct AccessorTableKey : IEquatable<AccessorTableKey>
192186
{
193187
public AccessorTableKey(SourceValueType sourceValueType, Type type, string name)
194188
{
195-
Invariant.Assert(type != null && type != null);
189+
Invariant.Assert(type != null);
196190

197191
_sourceValueType = sourceValueType;
198192
_type = type;
199193
_name = name;
200194
}
201195

202-
public override bool Equals(object o)
203-
{
204-
if (o is AccessorTableKey)
205-
return this == (AccessorTableKey)o;
206-
else
207-
return false;
208-
}
196+
public override bool Equals(object o) => o is AccessorTableKey other && Equals(other);
209197

210-
public static bool operator ==(AccessorTableKey k1, AccessorTableKey k2)
211-
{
212-
return k1._sourceValueType == k2._sourceValueType
213-
&& k1._type == k2._type
214-
&& k1._name == k2._name;
215-
}
198+
public bool Equals(AccessorTableKey other) =>
199+
_sourceValueType == other._sourceValueType
200+
&& _type == other._type
201+
&& _name == other._name;
216202

217-
public static bool operator !=(AccessorTableKey k1, AccessorTableKey k2)
218-
{
219-
return !(k1 == k2);
220-
}
203+
public static bool operator ==(AccessorTableKey k1, AccessorTableKey k2) => k1.Equals(k2);
221204

222-
public override int GetHashCode()
223-
{
224-
return unchecked(_type.GetHashCode() + _name.GetHashCode());
225-
}
205+
public static bool operator !=(AccessorTableKey k1, AccessorTableKey k2) => !k1.Equals(k2);
226206

227-
SourceValueType _sourceValueType;
228-
Type _type;
229-
string _name;
207+
public override int GetHashCode() => unchecked(_type.GetHashCode() + _name.GetHashCode());
208+
209+
private readonly SourceValueType _sourceValueType;
210+
private readonly Type _type;
211+
private readonly string _name;
230212
}
231213
}
232214
}
233-

src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingOperations.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ internal static bool Cleanup()
491491
}
492492

493493
// Print various interesting statistics
494+
[Conditional("DEBUG")]
494495
internal static void PrintStats()
495496
{
496497
DataBindEngine.CurrentDataBindEngine.AccessorTable.PrintStats();

0 commit comments

Comments
 (0)