Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonDocument Working PR #2

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4a6719d
Initial port of JsonObject (as JsonDocument/JsonElement)
bartonjs Nov 30, 2018
15854b1
Port JsonObjectTests -> JsonDocumentTests
bartonjs Dec 3, 2018
4958d2e
All ported tests pass
bartonjs Dec 4, 2018
12ed363
Off by one error in StartArray
bartonjs Dec 4, 2018
4c2e256
Minor test cleanup
bartonjs Dec 4, 2018
55e51fc
Make ToString work on Object and Array
bartonjs Dec 4, 2018
9c723cd
Initial performance tests for JsonDocument
bartonjs Dec 5, 2018
67765e0
Add some tests to touch every element
bartonjs Dec 6, 2018
f3f6a92
HasChildren => HasComplexChildren and use it for a faster indexer
bartonjs Dec 6, 2018
b6bcc40
Remove explicit casts in favor of the named methods.
bartonjs Dec 6, 2018
7f0682b
Remove custom pooling from CustomDb since JsonDocument dropped it
bartonjs Dec 6, 2018
8cbb903
TryGetRawData<T> => TryGetRawValue(non-generic)
bartonjs Dec 6, 2018
079fa8b
Remove ported diagnostic message from CustomStack
bartonjs Dec 6, 2018
63270d3
Make JsonElement readonly
bartonjs Dec 11, 2018
b24e65f
Address missing test changes
bartonjs Dec 11, 2018
48bc093
Clean up CustomDb
bartonjs Dec 11, 2018
148dd59
Don't need to clear the temporary transcode buffer
bartonjs Dec 11, 2018
b340315
Add GetPropertyName and GetPropertyValue
bartonjs Dec 11, 2018
60331b3
Code cleanup in JsonDocument
bartonjs Dec 11, 2018
0b8cc8f
Switch to validating UTF-8 encoder
bartonjs Dec 11, 2018
a9bbd87
Make PropertyName.ToString do what was discussed
bartonjs Dec 11, 2018
c2e0e6b
Add Parse overloads that work on string and ROS-char
bartonjs Dec 12, 2018
15d0f80
Disallow preserving comments into the DOM
bartonjs Dec 12, 2018
ebe1609
Use the default max depth
bartonjs Dec 12, 2018
6b87961
Add more tests
bartonjs Dec 12, 2018
4e4aa16
Make JsonReaderOptions optional
bartonjs Dec 12, 2018
64531a3
Remove TryGetRawValue and TryCopyRawValue
bartonjs Dec 12, 2018
7255c4f
Outline CheckValidInstance
bartonjs Dec 12, 2018
343d706
Outline CheckNotDisposed and CheckExpectedType
bartonjs Dec 12, 2018
e44beac
Separate EnumerateArray and EnumerateObject, implement IEnumera-inter…
bartonjs Dec 12, 2018
41f644c
TryGetValue => TryGet[type]
bartonjs Dec 12, 2018
c3b0d30
Add uint and float support
bartonjs Dec 12, 2018
c14edf5
Add support for ReadOnlySequence<byte>
bartonjs Dec 12, 2018
4dbc498
Move CustomDb initial size calculation into its ctor
bartonjs Dec 12, 2018
a106723
Support GetString on JsonTokenType.Null
bartonjs Dec 12, 2018
6a036db
Remove some dead code
bartonjs Dec 12, 2018
e209da5
Some missed outlining
bartonjs Dec 12, 2018
58c1e5b
More tests (and fixes)
bartonjs Dec 14, 2018
5cc921b
Make property matching match on the last one
bartonjs Dec 14, 2018
68efe04
Add more tests for the iterators
bartonjs Dec 17, 2018
e0800a4
Introduce a new enum for JsonElement.Type
bartonjs Dec 17, 2018
b3b2fa8
Add a test that uses multiple object lookups before changing that ind…
bartonjs Dec 17, 2018
8438baa
Improve legibility of fields in DbRow
bartonjs Dec 18, 2018
b0ed42c
Replace string indexers with GetProperty methods
bartonjs Dec 19, 2018
8871654
Add JsonElement.[Try]GetDecimal
bartonjs Dec 19, 2018
470449e
Fix optimized indexer for simple arrays
bartonjs Dec 20, 2018
12ef0c5
Ensure coverage for resized database
bartonjs Dec 20, 2018
5492aba
Make JsonProperty a readonly struct
bartonjs Jan 2, 2019
ab87420
Add Parse(Stream) and ParseAsync(Stream)
bartonjs Jan 9, 2019
bde6e70
Be consistent with the sealed keyword.
bartonjs Jan 9, 2019
b5da8ff
Some tweaks to ParseAsync
bartonjs Jan 9, 2019
d4131ac
Test it or it's not real.
bartonjs Jan 9, 2019
fa850b1
Add JsonElement.GetRawText() and JsonProperty.ToString
bartonjs Jan 10, 2019
069dbc4
SR all the things
bartonjs Jan 10, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/System.Text.Json/System.Text.Json.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Tests", "t
{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4} = {79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Performance.Tests", "tests\Performance\System.Text.Json.Performance.Tests.csproj", "{FB3EA273-567D-414F-B36D-3698BE8D198B}"
ProjectSection(ProjectDependencies) = postProject
{DF73E985-E143-4BF5-9FA4-E199E7D36235} = {DF73E985-E143-4BF5-9FA4-E199E7D36235}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "src\System.Text.Json.csproj", "{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4}"
ProjectSection(ProjectDependencies) = postProject
{6371299B-8F39-4A0A-A9CD-70F80FF205F6} = {6371299B-8F39-4A0A-A9CD-70F80FF205F6}
Expand All @@ -30,6 +35,10 @@ Global
{5F553243-042C-45C0-8E49-C739131E11C3}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{5F553243-042C-45C0-8E49-C739131E11C3}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{5F553243-042C-45C0-8E49-C739131E11C3}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4}.Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Debug|Any CPU
{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU
{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU
Expand All @@ -44,6 +53,7 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5F553243-042C-45C0-8E49-C739131E11C3} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{FB3EA273-567D-414F-B36D-3698BE8D198B} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{79E7EE4E-E8DF-4D67-B103-6930DAAF6EF4} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
{6371299B-8F39-4A0A-A9CD-70F80FF205F6} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
EndGlobalSection
Expand Down
43 changes: 43 additions & 0 deletions src/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,49 @@ public enum JsonCommentHandling : byte
Disallow = (byte)0,
Skip = (byte)2,
}
public sealed partial class JsonDocument : System.IDisposable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it going to be strange for folks to need to dispose an XxDocument? Is that required for other XxDocument in the framework? Is it required for any other JSON document-like types in other JSON libraries?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the DOM APIs aren't IDisposable, but they have readers that are. So "no, but nearly". It seems like a natural consequence of the performance goals.

If it's "too strange for the surface area" then the next option that I see is to use generic List; the consequence of not disposing in the current model being that the document "stole" the array from the pool rather than rented it.

{
internal JsonDocument() { }
public System.Text.Json.JsonElement RootElement { get { throw null; } }
public void Dispose() { }
public static System.Text.Json.JsonDocument Parse(System.ReadOnlyMemory<byte> utf8Json, System.Text.Json.JsonReaderOptions readerOptions) { throw null; }
}
public partial struct JsonElement
{
private object _dummy;
public System.Text.Json.JsonElement this[int index] { get { throw null; } }
public System.Text.Json.JsonElement this[System.ReadOnlySpan<byte> utf8PropertyName] { get { throw null; } }
public System.Text.Json.JsonElement this[System.ReadOnlySpan<char> propertyName] { get { throw null; } }
public System.Text.Json.JsonElement this[string propertyName] { get { throw null; } }
public System.Text.Json.JsonTokenType Type { get { throw null; } }
public System.Text.Json.JsonElement.ChildEnumerator EnumerateChildren() { throw null; }
public int GetArrayLength() { throw null; }
public bool GetBoolean() { throw null; }
public double GetDouble() { throw null; }
public int GetInt32() { throw null; }
public long GetInt64() { throw null; }
public string GetString() { throw null; }
[System.CLSCompliantAttribute(false)]
public ulong GetUInt64() { throw null; }
public override string ToString() { throw null; }
public bool TryCopyRawValue(System.Span<byte> destination, out int bytesWritten) { throw null; }
public bool TryGetProperty(System.ReadOnlySpan<byte> utf8PropertyName, out System.Text.Json.JsonElement value) { throw null; }
public bool TryGetProperty(System.ReadOnlySpan<char> propertyName, out System.Text.Json.JsonElement value) { throw null; }
public bool TryGetProperty(string propertyName, out System.Text.Json.JsonElement value) { throw null; }
public bool TryGetRawValue(out System.ReadOnlyMemory<byte> rawValue) { throw null; }
public bool TryGetValue(out double value) { throw null; }
public bool TryGetValue(out int value) { throw null; }
public bool TryGetValue(out long value) { throw null; }
[System.CLSCompliantAttribute(false)]
public bool TryGetValue(out ulong value) { throw null; }
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
public partial struct ChildEnumerator
{
private object _dummy;
public System.Text.Json.JsonElement Current { get { throw null; } }
public System.Text.Json.JsonElement.ChildEnumerator GetEnumerator() { throw null; }
public bool MoveNext() { throw null; }
}
}
public sealed partial class JsonReaderException : System.Exception
{
public JsonReaderException(string message, long lineNumber, long bytePositionInLine) { }
Expand Down
7 changes: 7 additions & 0 deletions src/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
<Compile Include="System\Text\Json\ConsumeTokenResult.cs" />
<Compile Include="System\Text\Json\JsonCommentHandling.cs" />
<Compile Include="System\Text\Json\JsonConstants.cs" />
<Compile Include="System\Text\Json\JsonDocument.cs" />
<Compile Include="System\Text\Json\JsonDocument.CustomDb.cs" />
<Compile Include="System\Text\Json\JsonDocument.CustomStack.cs" />
<Compile Include="System\Text\Json\JsonDocument.DbRow.cs" />
<Compile Include="System\Text\Json\JsonDocument.StackRow.cs" />
<Compile Include="System\Text\Json\JsonDocument.TryGetProperty.cs" />
<Compile Include="System\Text\Json\JsonElement.cs" />
<Compile Include="System\Text\Json\JsonReaderException.cs" />
<Compile Include="System\Text\Json\JsonReaderHelper.cs" />
<Compile Include="System\Text\Json\JsonReaderOptions.cs" />
Expand Down
30 changes: 27 additions & 3 deletions src/System.Text.Json/src/System/Text/Json/BitStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,17 @@ public bool Pop()
}
else
{
inObject = PopFromArray();
inObject = ReadFromArray(_currentDepth);
}
return inObject;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private bool PopFromArray()
private bool ReadFromArray(int currentDepth)
{
int index = _currentDepth - AllocationFreeMaxDepth - 1;
Debug.Assert(currentDepth == _currentDepth || currentDepth == _currentDepth - 1);

int index = currentDepth - AllocationFreeMaxDepth - 1;
Debug.Assert(_array != null);
Debug.Assert(index >= 0, $"Get - Negative - index: {index}, arrayLength: {_array.Length}");

Expand All @@ -133,6 +135,28 @@ private bool PopFromArray()
return (_array[elementIndex] & (1 << extraBits)) != 0;
}


internal bool Peek()
{
int depth = _currentDepth - 1;

if (depth < AllocationFreeMaxDepth)
{
// Pop would shift, then return the least significant bit.
// So Peek needs to check the unshifted position.
return (_allocationFreeContainer & 0b0010) != 0;
}

if (depth == AllocationFreeMaxDepth)
{
// Pop would not shift, but just return the least significant bit.
// So do that.
return (_allocationFreeContainer & 1) != 0;
}

return ReadFromArray(depth);
}

private void DoubleArray(int minSize)
{
Debug.Assert(_array.Length < int.MaxValue / 2, $"Array too large - arrayLength: {_array.Length}");
Expand Down
203 changes: 203 additions & 0 deletions src/System.Text.Json/src/System/Text/Json/JsonDocument.CustomDb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Text.Json
{
partial class JsonDocument
{
private struct CustomDb : IDisposable
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
{
private const int SizeOrLengthOffset = 4;
private const int NumberOfRowsOffset = 8;

internal int Length;
private byte[] _rentedBuffer;

internal CustomDb(int initialSize)
{
_rentedBuffer = ArrayPool<byte>.Shared.Rent(initialSize);
Length = 0;
}

public void Dispose()
{
if (_rentedBuffer == null)
{
return;
}

ArrayPool<byte>.Shared.Return(_rentedBuffer);
_rentedBuffer = null;
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
Length = 0;
}

internal void TrimExcess()
{
if (Length <= _rentedBuffer.Length / 2)
{
byte[] newRent = ArrayPool<byte>.Shared.Rent(Length);
byte[] returnBuf = newRent;

if (newRent.Length < _rentedBuffer.Length)
{
Buffer.BlockCopy(_rentedBuffer, 0, newRent, 0, Length);
returnBuf = _rentedBuffer;
_rentedBuffer = newRent;
}

ArrayPool<byte>.Shared.Return(returnBuf);
}
}

internal void Append(JsonTokenType tokenType, int startLocation, int length, bool isPropertyValue)
{
// StartArray or StartObject should have length -1, otherwise the length should not be -1.
Debug.Assert(
(tokenType == JsonTokenType.StartArray || tokenType == JsonTokenType.StartObject) ==
(length == DbRow.UnknownSize));

if (Length >= _rentedBuffer.Length - DbRow.Size)
{
Enlarge();
}

DbRow row = new DbRow(tokenType, startLocation, length, isPropertyValue);
MemoryMarshal.Write(_rentedBuffer.AsSpan(Length), ref row);
Length += DbRow.Size;
}

private void Enlarge()
{
int size = _rentedBuffer.Length * 2;
byte[] newArray = ArrayPool<byte>.Shared.Rent(size);
Buffer.BlockCopy(_rentedBuffer, 0, newArray, 0, Length);
ArrayPool<byte>.Shared.Return(_rentedBuffer);
_rentedBuffer = newArray;
}

[Conditional("DEBUG")]
private void AssertValidIndex(int index)
{
Debug.Assert(index >= 0);
Debug.Assert(index <= Length - DbRow.Size, $"index {index} is out of bounds");
Debug.Assert(index % DbRow.Size == 0, $"index {index} is not at a record start position");
}

internal void SetLength(int index, int length)
{
AssertValidIndex(index);
Debug.Assert(length >= 0);
Span<byte> destination = _rentedBuffer.AsSpan(index + 4);
int cur = MemoryMarshal.Read<int>(destination);

// Persist the most significant bit
length |= (cur & unchecked((int)0x80000000));
MemoryMarshal.Write(destination, ref length);
}

internal void SetNumberOfRows(int index, int numberOfRows)
{
AssertValidIndex(index);
Debug.Assert(numberOfRows >= 1 && numberOfRows <= 0x0FFFFFFF);

Span<byte> dataPos = _rentedBuffer.AsSpan(index + NumberOfRowsOffset);
int current = MemoryMarshal.Read<int>(dataPos);

// Persist the most significant nybble
int value = (current & unchecked((int)0xF0000000)) | numberOfRows;
MemoryMarshal.Write(dataPos, ref value);
}

internal void SetHasComplexChildren(int index)
{
AssertValidIndex(index);

// The HasComplexChildren bit is the most significant bit of "SizeOrLength"
Span<byte> dataPos = _rentedBuffer.AsSpan(index + SizeOrLengthOffset);
int current = MemoryMarshal.Read<int>(dataPos);

int value = current | unchecked((int)0x80000000);
MemoryMarshal.Write(dataPos, ref value);
}

internal int FindIndexOfFirstUnsetSizeOrLength(JsonTokenType lookupType)
{
Debug.Assert(lookupType == JsonTokenType.StartObject || lookupType == JsonTokenType.StartArray);
return FindOpenElement(lookupType);
}

private int FindOpenElement(JsonTokenType lookupType)
{
Span<byte> data = _rentedBuffer.AsSpan(0, Length);

for (int i = Length - DbRow.Size; i >= 0; i -= DbRow.Size)
{
DbRow row = MemoryMarshal.Read<DbRow>(data.Slice(i));

if (row.IsUnknownSize && row.TokenType == lookupType)
{
return i;
}
}

// We should never reach here.
Debug.Fail($"Unable to find expected {lookupType} token");
return -1;
}

internal void Get(int index, out DbRow row)
{
AssertValidIndex(index);
row = MemoryMarshal.Read<DbRow>(_rentedBuffer.AsSpan(index));
}

internal int GetLocation() => MemoryMarshal.Read<int>(_rentedBuffer.AsSpan());

internal int GetLocation(int index)
{
AssertValidIndex(index);
return MemoryMarshal.Read<int>(_rentedBuffer.AsSpan()) & int.MaxValue;
}

internal int GetSizeOrLength(int index)
{
AssertValidIndex(index);
return MemoryMarshal.Read<int>(_rentedBuffer.AsSpan(index + SizeOrLengthOffset)) & int.MaxValue;
}

internal JsonTokenType GetJsonTokenType(int index = 0)
{
AssertValidIndex(index);
uint union = MemoryMarshal.Read<uint>(_rentedBuffer.AsSpan(index + NumberOfRowsOffset));

return (JsonTokenType)(union >> 28);
}

#if DIAGNOSTIC
internal string PrintDatabase()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Index Offset SizeOrLen TokenType Child IPV NumRows");
Span<byte> data = _rentedBuffer;

for (int i = 0; i < Length; i += DbRow.Size)
{
DbRow record = MemoryMarshal.Read<DbRow>(data.Slice(i));
sb.Append($"{i:D6} {record.Location:D7} {record.SizeOrLength:D10} ");
sb.Append(record.TokenType.ToString().PadRight(13));
sb.Append(record.HasChildren.ToString().PadRight(6));
sb.Append(record.IsPropertyValue.ToString().PadRight(6));
sb.AppendLine("" + record.NumberOfRows);
}

return sb.ToString();
}
#endif
}
}
}
Loading