Skip to content

Commit b36ad30

Browse files
committed
Small cleanup refactoring follow up for pull request 113
1. Factored out four branches in BsonTrieNode.GetChild a. Eliminated the need to check _isFrozen b. Eliminated the need to check _minKeyByte c. Eliminated a CLR bounds check on _keyByteIndexes d. Eliminated a CLR bounds check on _children 2. Factored out the maxKeyByte member in BsonTrieNode 3. Refactored a foreach into a for in BsonTrie.TryGetValue (for is faster than foreach on arrays due to better CLR support) 4. Made BsonBuffer.DecodeUtf8String static 5. Factored out a branch in BsonBuffer.DecodeUtf8String a. Eliminated a CLR bounds check on __asciiStringTable 6. Updated BsonBuffer.ReadCString to use an Int32 instead of an Object for the default type (carries no GC checking overhead and is the default .Net register type) 7. Fixed a comment typo in BsonTrie.TryGetValue 8. Fixed an XML comment error 9. Switched BsonTrieNode._children from List to Array (Lists are slower than Arrays)
1 parent 6c270e2 commit b36ad30

File tree

4 files changed

+119
-89
lines changed

4 files changed

+119
-89
lines changed

Bson/IO/BsonBuffer.cs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ public string ReadString()
557557
public string ReadCString()
558558
{
559559
bool found;
560-
object value;
560+
int value;
561561
return ReadCString(null, out found, out value);
562562
}
563563

@@ -960,28 +960,57 @@ public void WriteZero()
960960
}
961961
}
962962

963-
// private methods
964-
private string DecodeUtf8String(byte[] buffer, int index, int count)
963+
// private static methods
964+
private static string DecodeUtf8String(byte[] buffer, int index, int count)
965965
{
966966
switch (count)
967967
{
968968
// special case empty strings
969969
case 0:
970-
return "";
970+
return string.Empty;
971971

972972
// special case single character strings
973973
case 1:
974-
var byte1 = (int)buffer[index];
975-
if (byte1 < __asciiStringTable.Length)
974+
var byte1 = buffer[index];
975+
// enable the .Net CLR to eliminate an array bounds check on __asciiStringTable
976+
var asciiStringTable = __asciiStringTable;
977+
if (byte1 < asciiStringTable.Length)
976978
{
977-
return __asciiStringTable[byte1];
979+
return asciiStringTable[byte1];
978980
}
979981
break;
980982
}
981983

982984
return __utf8Encoding.GetString(buffer, index, count);
983985
}
984986

987+
private static int IndexOfNull<TValue>(
988+
byte[] buffer,
989+
int index,
990+
int count,
991+
ref BsonTrieNode<TValue> bsonTrieNode)
992+
{
993+
for (; count > 0; index++, count--)
994+
{
995+
// bsonTrieNode might be null on entry or it might become null while navigating the trie
996+
if (bsonTrieNode == null)
997+
{
998+
return Array.IndexOf<byte>(buffer, 0, index, count);
999+
}
1000+
1001+
var keyByte = buffer[index];
1002+
if (keyByte == 0)
1003+
{
1004+
return index;
1005+
}
1006+
1007+
bsonTrieNode = bsonTrieNode.GetChild(keyByte); // might return null
1008+
}
1009+
1010+
return -1;
1011+
}
1012+
1013+
// private methods
9851014
private void EnsureDataAvailable(int needed)
9861015
{
9871016
if (_length - _position < needed)
@@ -1013,31 +1042,5 @@ private void EnsureSpaceAvailable(int needed)
10131042
}
10141043
}
10151044
}
1016-
1017-
private static int IndexOfNull<TValue>(
1018-
byte[] buffer,
1019-
int index,
1020-
int count,
1021-
ref BsonTrieNode<TValue> bsonTrieNode)
1022-
{
1023-
for (; count > 0; index++, count--)
1024-
{
1025-
// bsonTrieNode might be null on entry or it might become null while navigating the trie
1026-
if (bsonTrieNode == null)
1027-
{
1028-
return Array.IndexOf<byte>(buffer, 0, index, count);
1029-
}
1030-
1031-
var keyByte = buffer[index];
1032-
if (keyByte == 0)
1033-
{
1034-
return index;
1035-
}
1036-
1037-
bsonTrieNode = bsonTrieNode.GetChild(keyByte); // might return null
1038-
}
1039-
1040-
return -1;
1041-
}
10421045
}
10431046
}

Bson/IO/BsonTrie.cs

Lines changed: 81 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Linq;
1918
using System.Text;
2019

2120
namespace MongoDB.Bson.IO
@@ -92,7 +91,7 @@ public void Freeze()
9291
}
9392

9493
/// <summary>
95-
/// Gets the value associated with the specifie element name.
94+
/// Gets the value associated with the specified element name.
9695
/// </summary>
9796
/// <param name="elementName">The element name.</param>
9897
/// <param name="value">
@@ -105,25 +104,24 @@ public bool TryGetValue(string elementName, out TValue value)
105104
var keyBytes = __utf8Encoding.GetBytes(elementName);
106105

107106
var node = _root;
108-
foreach (var keyByte in keyBytes)
107+
for (var i = 0; i < keyBytes.Length; i++)
109108
{
110-
node = node.GetChild(keyByte);
109+
node = node.GetChild(keyBytes[i]);
111110
if (node == null)
112111
{
113-
break;
112+
value = default(TValue);
113+
return false;
114114
}
115115
}
116116

117-
if (node != null && node.HasValue)
118-
{
119-
value = node.Value;
120-
return true;
121-
}
122-
else
117+
if (!node.HasValue)
123118
{
124119
value = default(TValue);
125120
return false;
126121
}
122+
123+
value = node.Value;
124+
return true;
127125
}
128126
}
129127

@@ -137,12 +135,11 @@ public sealed class BsonTrieNode<TValue>
137135
private string _elementName;
138136
private TValue _value;
139137
private BsonTrieNode<TValue> _onlyChild; // used when there is only one child
140-
private List<BsonTrieNode<TValue>> _children; // used when there are two or more children
138+
private BsonTrieNode<TValue>[] _children; // used when there are two or more children
141139

142140
// private fields set when node is frozen
143141
private bool _isFrozen;
144142
private byte _minKeyByte;
145-
private byte _maxKeyByte;
146143
private byte[] _keyByteIndexes; // maps key bytes into indexes into the _children list
147144

148145
// constructors
@@ -212,23 +209,19 @@ public BsonTrieNode<TValue> GetChild(byte keyByte)
212209
}
213210
else if (_children != null)
214211
{
215-
if (_isFrozen)
212+
var index = (uint)((int)keyByte - _minKeyByte);
213+
// enable the .Net CLR to eliminate an array bounds check on _keyByteIndexes
214+
var keyByteIndexes = _keyByteIndexes;
215+
if (index < keyByteIndexes.Length)
216216
{
217-
// once the node is frozen we can use a faster lookup based on fields initialized when Freeze was called
218-
if (keyByte >= _minKeyByte && keyByte <= _maxKeyByte)
217+
index = keyByteIndexes[index];
218+
// enable the .Net CLR to eliminate an array bounds check on _children
219+
var children = _children;
220+
if (index < children.Length)
219221
{
220-
var index = _keyByteIndexes[keyByte - _minKeyByte];
221-
if (index < _children.Count)
222-
{
223-
return _children[index];
224-
}
222+
return children[index];
225223
}
226224
}
227-
else
228-
{
229-
// until the node is frozen do a simple linear scan of the _children list
230-
return _children.Where(child => child._keyByte == keyByte).SingleOrDefault();
231-
}
232225
}
233226
return null;
234227
}
@@ -237,35 +230,39 @@ public BsonTrieNode<TValue> GetChild(byte keyByte)
237230
internal void AddChild(BsonTrieNode<TValue> child)
238231
{
239232
if (_isFrozen) { throw new InvalidOperationException("BsonTrieNode is frozen."); }
240-
if (_children == null)
233+
if (_children != null)
241234
{
242-
if (_onlyChild == null)
235+
if (_children[child._keyByte] != null)
243236
{
244-
_onlyChild = child;
237+
throw new ArgumentException("BsonTrieNode already contains a child with the same keyByte.");
245238
}
246-
else
239+
240+
_children[child._keyByte] = child;
241+
}
242+
else if (_onlyChild != null)
243+
{
244+
if (_onlyChild._keyByte == child._keyByte)
247245
{
248-
if (_onlyChild._keyByte == child._keyByte)
249-
{
250-
throw new ArgumentException("BsonTrieNode already contains a child with the same keyByte");
251-
}
246+
throw new ArgumentException("BsonTrieNode already contains a child with the same keyByte.");
247+
}
252248

253-
var children = new List<BsonTrieNode<TValue>>();
254-
children.Add(_onlyChild);
255-
children.Add(child);
249+
var children = new BsonTrieNode<TValue>[256];
250+
children[_onlyChild._keyByte] = _onlyChild;
251+
children[child._keyByte] = child;
256252

257-
_onlyChild = null;
258-
_children = children;
253+
var keyByteIndexes = new byte[256];
254+
for (var i = 0; i < keyByteIndexes.Length; i++)
255+
{
256+
keyByteIndexes[i] = (byte)i;
259257
}
258+
259+
_keyByteIndexes = keyByteIndexes;
260+
_onlyChild = null;
261+
_children = children;
260262
}
261263
else
262264
{
263-
if (_children.Any(n => n._keyByte == child._keyByte))
264-
{
265-
throw new ArgumentException("BsonTrieNode already contains a child with the same keyByte");
266-
}
267-
268-
_children.Add(child);
265+
_onlyChild = child;
269266
}
270267
}
271268

@@ -279,18 +276,48 @@ internal void Freeze()
279276
}
280277
else if (_children != null)
281278
{
282-
_children.ForEach(child => child.Freeze());
283-
_minKeyByte = _children.Min(n => n._keyByte);
284-
_maxKeyByte = _children.Max(n => n._keyByte);
285-
_keyByteIndexes = new byte[_maxKeyByte - _minKeyByte + 1];
286-
for (var index = 0; index < _keyByteIndexes.Length; index++)
279+
var i = 0;
280+
281+
// _children is guaranteed to have at least one element that isn't null
282+
for (; _children[i] == null; i++);
283+
284+
var minKeyByte = (byte)i;
285+
var maxKeyByte = minKeyByte;
286+
287+
byte childIndex = 0;
288+
289+
for (i++; i < _children.Length; i++)
290+
{
291+
if (_children[i] != null)
292+
{
293+
maxKeyByte = (byte)i;
294+
childIndex++;
295+
}
296+
}
297+
298+
var keyByteIndexes = new byte[(int)maxKeyByte - minKeyByte + 1];
299+
for (i = 0; i < keyByteIndexes.Length; i++)
287300
{
288-
_keyByteIndexes[index] = 255; // make sure unused entries can be identified
301+
keyByteIndexes[i] = 255; // make sure unused entries can be identified
289302
}
290-
for (var index = 0; index < _children.Count; index++)
303+
304+
var children = new BsonTrieNode<TValue>[(int)childIndex + 1];
305+
childIndex = 0;
306+
for (i = 0; i < _children.Length; i++)
291307
{
292-
_keyByteIndexes[_children[index]._keyByte - _minKeyByte] = (byte)index;
308+
var child = _children[i];
309+
if (child != null)
310+
{
311+
keyByteIndexes[child._keyByte - minKeyByte] = childIndex;
312+
children[childIndex] = child;
313+
child.Freeze();
314+
childIndex++;
315+
}
293316
}
317+
318+
_minKeyByte = minKeyByte;
319+
_keyByteIndexes = keyByteIndexes;
320+
_children = children;
294321
}
295322
_isFrozen = true;
296323
}
@@ -306,6 +333,7 @@ internal void SetValue(string elementName, TValue value)
306333
{
307334
throw new InvalidOperationException("BsonTrieNode already has a value.");
308335
}
336+
if (_isFrozen) { throw new InvalidOperationException("BsonTrieNode is frozen."); }
309337

310338
_elementName = elementName;
311339
_value = value;

Bson/ObjectModel/BsonDocumentWrapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public BsonDocumentWrapper(object wrappedObject)
6363
/// </summary>
6464
/// <param name="wrappedNominalType">The nominal type of the wrapped object.</param>
6565
/// <param name="wrappedObject">The wrapped object.</param>
66-
public BsonDocumentWrapper(Type wrappedNominalType, object value)
67-
: this(wrappedNominalType, value, false)
66+
public BsonDocumentWrapper(Type wrappedNominalType, object wrappedObject)
67+
: this(wrappedNominalType, wrappedObject, false)
6868
{
6969
}
7070

Bson/Serialization/BsonClassMap.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,6 @@ private void AutoMapMembers()
10921092
{
10931093
_declaredMemberMaps.Sort((x, y) => x.Order.CompareTo(y.Order));
10941094
}
1095-
10961095
}
10971096
}
10981097

0 commit comments

Comments
 (0)