Skip to content

Commit 55c13cd

Browse files
authored
CSHARP-5348: Avoid allocations for Bson*Context (#1791)
1 parent c979cd5 commit 55c13cd

File tree

6 files changed

+220
-236
lines changed

6 files changed

+220
-236
lines changed

src/MongoDB.Bson/IO/BsonBinaryReader.cs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Collections.Generic;
1718
using System.Globalization;
1819
using System.IO;
1920

@@ -30,6 +31,7 @@ public class BsonBinaryReader : BsonReader
3031
#pragma warning restore CA2213 // Disposable never disposed
3132
private readonly BsonStream _bsonStream;
3233
private BsonBinaryReaderContext _context;
34+
private readonly Stack<BsonBinaryReaderContext> _contextStack = new(4);
3335

3436
// constructors
3537
/// <summary>
@@ -61,7 +63,7 @@ public BsonBinaryReader(Stream stream, BsonBinaryReaderSettings settings)
6163
_baseStream = stream;
6264
_bsonStream = (stream as BsonStream) ?? new BsonStreamAdapter(stream);
6365

64-
_context = new BsonBinaryReaderContext(null, ContextType.TopLevel, 0, 0);
66+
_context = new BsonBinaryReaderContext(ContextType.TopLevel, 0, 0);
6567
}
6668

6769
// public properties
@@ -109,10 +111,8 @@ public override void Close()
109111
/// Gets a bookmark to the reader's current position and state.
110112
/// </summary>
111113
/// <returns>A bookmark.</returns>
112-
public override BsonReaderBookmark GetBookmark()
113-
{
114-
return new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, _bsonStream.Position);
115-
}
114+
public override BsonReaderBookmark GetBookmark() =>
115+
new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, _contextStack, _bsonStream.Position);
116116

117117
/// <summary>
118118
/// Determines whether this reader is at end of file.
@@ -201,12 +201,11 @@ public override BsonType ReadBsonType()
201201

202202
if (_context.ContextType == ContextType.Array)
203203
{
204-
_context.CurrentArrayIndex++;
204+
_context.ArrayIndex++;
205205
}
206206

207207
try
208208
{
209-
210209
CurrentBsonType = _bsonStream.ReadBsonType();
211210
}
212211
catch (FormatException ex)
@@ -342,7 +341,8 @@ public override void ReadEndArray()
342341
ThrowInvalidState("ReadEndArray", BsonReaderState.EndOfArray);
343342
}
344343

345-
_context = _context.PopContext(_bsonStream.Position);
344+
PopContext();
345+
346346
switch (_context.ContextType)
347347
{
348348
case ContextType.Array: State = BsonReaderState.Type; break;
@@ -371,10 +371,10 @@ public override void ReadEndDocument()
371371
ThrowInvalidState("ReadEndDocument", BsonReaderState.EndOfDocument);
372372
}
373373

374-
_context = _context.PopContext(_bsonStream.Position);
374+
PopContext();
375375
if (_context.ContextType == ContextType.JavaScriptWithScope)
376376
{
377-
_context = _context.PopContext(_bsonStream.Position); // JavaScriptWithScope
377+
PopContext(); // JavaScriptWithScope
378378
}
379379
switch (_context.ContextType)
380380
{
@@ -432,7 +432,9 @@ public override string ReadJavaScriptWithScope()
432432

433433
var startPosition = _bsonStream.Position; // position of size field
434434
var size = ReadSize();
435-
_context = new BsonBinaryReaderContext(_context, ContextType.JavaScriptWithScope, startPosition, size);
435+
436+
PushContext(new(ContextType.JavaScriptWithScope, startPosition, size));
437+
436438
var code = _bsonStream.ReadString(Settings.Encoding);
437439

438440
State = BsonReaderState.ScopeDocument;
@@ -486,7 +488,7 @@ public override string ReadName(INameDecoder nameDecoder)
486488

487489
if (_context.ContextType == ContextType.Document)
488490
{
489-
_context.CurrentElementName = CurrentName;
491+
_context.ElementName = CurrentName;
490492
}
491493

492494
return CurrentName;
@@ -553,7 +555,7 @@ public override IByteBuffer ReadRawBsonDocument()
553555

554556
if (_context.ContextType == ContextType.JavaScriptWithScope)
555557
{
556-
_context = _context.PopContext(_bsonStream.Position); // JavaScriptWithScope
558+
PopContext(); // JavaScriptWithScope
557559
}
558560
switch (_context.ContextType)
559561
{
@@ -590,7 +592,9 @@ public override void ReadStartArray()
590592

591593
var startPosition = _bsonStream.Position; // position of size field
592594
var size = ReadSize();
593-
_context = new BsonBinaryReaderContext(_context, ContextType.Array, startPosition, size);
595+
596+
PushContext(new(ContextType.Array, startPosition, size));
597+
594598
State = BsonReaderState.Type;
595599
}
596600

@@ -605,7 +609,9 @@ public override void ReadStartDocument()
605609
var contextType = (State == BsonReaderState.ScopeDocument) ? ContextType.ScopeDocument : ContextType.Document;
606610
var startPosition = _bsonStream.Position; // position of size field
607611
var size = ReadSize();
608-
_context = new BsonBinaryReaderContext(_context, contextType, startPosition, size);
612+
613+
PushContext(new(contextType, startPosition, size));
614+
609615
State = BsonReaderState.Type;
610616
}
611617

@@ -662,10 +668,11 @@ public override void ReadUndefined()
662668
public override void ReturnToBookmark(BsonReaderBookmark bookmark)
663669
{
664670
var binaryReaderBookmark = (BsonBinaryReaderBookmark)bookmark;
671+
665672
State = binaryReaderBookmark.State;
666673
CurrentBsonType = binaryReaderBookmark.CurrentBsonType;
667674
CurrentName = binaryReaderBookmark.CurrentName;
668-
_context = binaryReaderBookmark.CloneContext();
675+
_context = binaryReaderBookmark.RestoreContext(_contextStack);
669676
_bsonStream.Position = binaryReaderBookmark.Position;
670677
}
671678

@@ -686,7 +693,7 @@ public override void SkipName()
686693

687694
if (_context.ContextType == ContextType.Document)
688695
{
689-
_context.CurrentElementName = CurrentName;
696+
_context.ElementName = CurrentName;
690697
}
691698
}
692699

@@ -745,7 +752,7 @@ protected override void Dispose(bool disposing)
745752
{
746753
Close();
747754
}
748-
catch { } // ignore exceptions
755+
catch { /* ignore exceptions */ }
749756
}
750757
base.Dispose(disposing);
751758
}
@@ -767,35 +774,41 @@ private string GenerateDottedElementName()
767774
}
768775
else if (_context.ContextType == ContextType.Array)
769776
{
770-
elementName = _context.CurrentArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
777+
elementName = _context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
771778
}
772779
else
773780
{
774781
elementName = "?";
775782
}
776783

777-
return GenerateDottedElementName(_context.ParentContext, elementName);
784+
return GenerateDottedElementName(_contextStack.ToArray(), 0, elementName);
778785
}
779786

780-
private string GenerateDottedElementName(BsonBinaryReaderContext context, string elementName)
787+
private string GenerateDottedElementName(BsonBinaryReaderContext[] contexts, int currentContextIndex, string elementName)
781788
{
789+
if (currentContextIndex >= contexts.Length)
790+
return elementName;
791+
792+
var context = contexts[currentContextIndex];
793+
var nextIndex = currentContextIndex + 1;
794+
782795
if (context.ContextType == ContextType.Document)
783796
{
784-
return GenerateDottedElementName(context.ParentContext, (context.CurrentElementName ?? "?") + "." + elementName);
785-
}
786-
else if (context.ContextType == ContextType.Array)
787-
{
788-
var indexElementName = context.CurrentArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
789-
return GenerateDottedElementName(context.ParentContext, indexElementName + "." + elementName);
797+
return GenerateDottedElementName(contexts, nextIndex, (context.ElementName ?? "?") + "." + elementName);
790798
}
791-
else if (context.ParentContext != null)
799+
800+
if (context.ContextType == ContextType.Array)
792801
{
793-
return GenerateDottedElementName(context.ParentContext, "?." + elementName);
802+
var indexElementName = context.ArrayIndex.ToString(NumberFormatInfo.InvariantInfo);
803+
return GenerateDottedElementName(contexts, nextIndex, indexElementName + "." + elementName);
794804
}
795-
else
805+
806+
if (nextIndex < contexts.Length)
796807
{
797-
return elementName;
808+
return GenerateDottedElementName(contexts, nextIndex, "?." + elementName);
798809
}
810+
811+
return elementName;
799812
}
800813

801814
private BsonReaderState GetNextState()
@@ -813,6 +826,23 @@ private BsonReaderState GetNextState()
813826
}
814827
}
815828

829+
private void PopContext()
830+
{
831+
var actualSize = _bsonStream.Position - _context.StartPosition;
832+
if (actualSize != _context.Size)
833+
{
834+
throw new FormatException($"Expected size to be {_context.Size}, not {actualSize}.");
835+
}
836+
837+
_context =_contextStack.Pop();
838+
}
839+
840+
private void PushContext(BsonBinaryReaderContext newContext)
841+
{
842+
_contextStack.Push(_context);
843+
_context = newContext;
844+
}
845+
816846
private int ReadSize()
817847
{
818848
int size = _bsonStream.ReadInt32();

src/MongoDB.Bson/IO/BsonBinaryReaderBookmark.cs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,43 @@
1313
* limitations under the License.
1414
*/
1515

16-
namespace MongoDB.Bson.IO
16+
using System.Collections.Generic;
17+
namespace MongoDB.Bson.IO;
18+
19+
/// <summary>
20+
/// Represents a bookmark that can be used to return a reader to the current position and state.
21+
/// </summary>
22+
public class BsonBinaryReaderBookmark : BsonReaderBookmark
1723
{
18-
/// <summary>
19-
/// Represents a bookmark that can be used to return a reader to the current position and state.
20-
/// </summary>
21-
public class BsonBinaryReaderBookmark : BsonReaderBookmark
24+
private readonly BsonBinaryReaderContext _context;
25+
private readonly BsonBinaryReaderContext[] _contextArray;
26+
private readonly long _position;
27+
28+
internal BsonBinaryReaderBookmark(
29+
BsonReaderState state,
30+
BsonType currentBsonType,
31+
string currentName,
32+
BsonBinaryReaderContext currentContext,
33+
Stack<BsonBinaryReaderContext> contextStack,
34+
long position)
35+
: base(state, currentBsonType, currentName)
2236
{
23-
// private fields
24-
private BsonBinaryReaderContext _context;
25-
private long _position;
37+
_context = currentContext;
38+
_contextArray = contextStack.ToArray();
39+
_position = position;
40+
}
2641

27-
// constructors
28-
internal BsonBinaryReaderBookmark(
29-
BsonReaderState state,
30-
BsonType currentBsonType,
31-
string currentName,
32-
BsonBinaryReaderContext context,
33-
long position)
34-
: base(state, currentBsonType, currentName)
35-
{
36-
_context = context.Clone();
37-
_position = position;
38-
}
42+
internal long Position => _position;
3943

40-
// internal properties
41-
internal long Position
42-
{
43-
get { return _position; }
44-
}
44+
internal BsonBinaryReaderContext RestoreContext(Stack<BsonBinaryReaderContext> contextStack)
45+
{
46+
contextStack.Clear();
4547

46-
// internal methods
47-
internal BsonBinaryReaderContext CloneContext()
48+
for (var i = _contextArray.Length - 1; i >= 0; i--)
4849
{
49-
return _context.Clone();
50+
contextStack.Push(_contextArray[i]);
5051
}
52+
53+
return _context;
5154
}
5255
}

src/MongoDB.Bson/IO/BsonBinaryReaderContext.cs

Lines changed: 10 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -13,75 +13,17 @@
1313
* limitations under the License.
1414
*/
1515

16-
using System;
16+
namespace MongoDB.Bson.IO;
1717

18-
namespace MongoDB.Bson.IO
18+
internal struct BsonBinaryReaderContext(
19+
ContextType contextType,
20+
long startPosition,
21+
long size)
1922
{
20-
internal class BsonBinaryReaderContext
21-
{
22-
// private fields
23-
private readonly BsonBinaryReaderContext _parentContext;
24-
private readonly ContextType _contextType;
25-
private readonly long _startPosition;
26-
private readonly long _size;
27-
private string _currentElementName;
28-
private int _currentArrayIndex = -1;
23+
public ContextType ContextType { get; } = contextType;
24+
public long StartPosition { get; } = startPosition;
25+
public long Size { get; } = size;
2926

30-
// constructors
31-
internal BsonBinaryReaderContext(
32-
BsonBinaryReaderContext parentContext,
33-
ContextType contextType,
34-
long startPosition,
35-
long size)
36-
{
37-
_parentContext = parentContext;
38-
_contextType = contextType;
39-
_startPosition = startPosition;
40-
_size = size;
41-
}
42-
43-
// public properties
44-
public ContextType ContextType
45-
{
46-
get { return _contextType; }
47-
}
48-
49-
public int CurrentArrayIndex
50-
{
51-
get { return _currentArrayIndex; }
52-
set { _currentArrayIndex = value; }
53-
}
54-
55-
public string CurrentElementName
56-
{
57-
get { return _currentElementName; }
58-
set { _currentElementName = value; }
59-
}
60-
61-
public BsonBinaryReaderContext ParentContext
62-
{
63-
get { return _parentContext; }
64-
}
65-
66-
// public methods
67-
/// <summary>
68-
/// Creates a clone of the context.
69-
/// </summary>
70-
/// <returns>A clone of the context.</returns>
71-
public BsonBinaryReaderContext Clone()
72-
{
73-
return new BsonBinaryReaderContext(_parentContext, _contextType, _startPosition, _size);
74-
}
75-
76-
public BsonBinaryReaderContext PopContext(long position)
77-
{
78-
var actualSize = position - _startPosition;
79-
if (actualSize != _size)
80-
{
81-
var message = string.Format("Expected size to be {0}, not {1}.", _size, actualSize);
82-
throw new FormatException(message);
83-
}
84-
return _parentContext;
85-
}
86-
}
27+
public int ArrayIndex { get; set; } = -1;
28+
public string ElementName { get; set; }
8729
}

0 commit comments

Comments
 (0)