Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5d09005
Speed up text encoding
Daniel-Svensson Jul 7, 2022
63c760c
Update implementation
Daniel-Svensson Jul 18, 2022
196ce48
Add tests for binary xml strings
Daniel-Svensson Jul 26, 2022
65e7029
Merge branch 'binary_xml_text' of https://github.com/Daniel-Svensson/…
Daniel-Svensson Jul 26, 2022
4d8078a
limit counting code to 256 bit vectors
Daniel-Svensson Jul 26, 2022
6e5aabb
reword comment
Daniel-Svensson Aug 3, 2022
70fa189
rename test
Daniel-Svensson Aug 3, 2022
b34d259
move bytesmax
Daniel-Svensson Aug 3, 2022
5df5ae0
Fix bytesMax after moving variable initialization
Daniel-Svensson Aug 4, 2022
a790fbb
use unicode escape value in test
Daniel-Svensson Aug 4, 2022
2b82ac8
fix test typo "*" -> "+"
Daniel-Svensson Aug 4, 2022
301e531
Update src/libraries/System.Private.DataContractSerialization/src/Sys…
Daniel-Svensson Aug 12, 2022
5a21306
Remvoe vectorized code from UnsafeGetUTF8Length
Daniel-Svensson Aug 12, 2022
8a3de26
Merge remote-tracking branch 'upstream/main' into binary_xml_text
Daniel-Svensson Aug 13, 2022
048cade
Fix overfload
Daniel-Svensson Sep 8, 2022
8297311
Merge commit '080f708e7018f6c0529b6c875a44d84fc4d74419' into binary_x…
Daniel-Svensson Oct 24, 2022
287e737
use for loop which seems faster
Daniel-Svensson Oct 24, 2022
0d2a9bb
merge up to net8 preview1
Daniel-Svensson Mar 3, 2023
ab29682
remove vector loop
Daniel-Svensson Mar 6, 2023
251391f
make sealed encoding to allow devirtualisation
Daniel-Svensson Mar 11, 2023
a590739
back some changes
Daniel-Svensson Mar 20, 2023
46b6314
use uint for UnsafeGetUTF8Chars comparison
Daniel-Svensson Mar 25, 2023
82f8880
revert more changes
Daniel-Svensson Mar 26, 2023
d78aade
Fix cutoff based on new measurements
Daniel-Svensson Mar 26, 2023
3b20be8
use BinaryPrimitives.ReverseEndianness as suggested
Daniel-Svensson Mar 26, 2023
9c86b05
Update cutoff from 24 to 32 chars before calling, due to regression f…
Daniel-Svensson Mar 27, 2023
ccfb008
Remove sealed encoding since it only improves XmlConvert
Daniel-Svensson Apr 2, 2023
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
Prev Previous commit
Next Next commit
merge up to net8 preview1
  • Loading branch information
Daniel-Svensson committed Mar 3, 2023
commit 0d2a9bb2b35ae5a1042a39f1138acbea0cbd9e9d
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ protected unsafe int UnsafeGetUnicodeChars(char* chars, int charCount, byte[] bu

protected unsafe int UnsafeGetUTF8Length(char* chars, int charCount)
{
return (_encoding ?? s_UTF8Encoding).GetByteCount(chars, charCount);
return (_encoding ?? DataContractSerializer.ValidatingUTF8).GetByteCount(chars, charCount);
}

protected unsafe int UnsafeGetUTF8Chars(char* chars, int charCount, byte[] buffer, int offset)
Expand Down Expand Up @@ -410,7 +410,7 @@ protected unsafe int UnsafeGetUTF8Chars(char* chars, int charCount, byte[] buffe

NonAscii:
byte* bytesMax = _bytes + buffer.Length - offset;
return (int)(bytes - _bytes) + (_encoding ?? s_UTF8Encoding).GetBytes(chars, (int)(charsMax - chars), bytes, (int)(bytesMax - bytes));
return (int)(bytes - _bytes) + (_encoding ?? DataContractSerializer.ValidatingUTF8).GetBytes(chars, (int)(charsMax - chars), bytes, (int)(bytesMax - bytes));
}
}
return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CBA80130-6773-4DF9-995C-DC6CBED89CB5} = {41101B02-36C9-476B-98D5-1A6E105BBF4A}
{8B069551-9B95-464E-BB40-C56817506FEC} = {41101B02-36C9-476B-98D5-1A6E105BBF4A}
{8FF5E841-29F6-4DB7-A4F8-9281FBDA0B9C} = {41101B02-36C9-476B-98D5-1A6E105BBF4A}
{45263D7D-249E-4810-8F7D-1DEF25515210} = {41101B02-36C9-476B-98D5-1A6E105BBF4A}
{E5DB95E1-94AA-405C-9FFE-09B1E2498EE2} = {18E62E91-73A2-48AE-BEFF-CE7C64DF759D}
{7DF41C40-FE5D-41DF-B106-3DD77BE4D4B5} = {18E62E91-73A2-48AE-BEFF-CE7C64DF759D}
{1392041A-E2CA-4553-BEAF-363974651B81} = {DB29DBEF-FA4E-4334-AFB8-BFB2DA82D1DE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,179 @@ public static void FragmentTest()
Assert.False(FragmentHelper.CanFragment(writer));
}

[Fact]
public static void BinaryWriter_PrimitiveTypes()
{
using MemoryStream ms = new();
using XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms);
writer.WriteStartElement("root");

AssertBytesWritten(x => x.WriteValue((byte)0x78), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 });
AssertBytesWritten(x => x.WriteValue((short)0x1234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 });
AssertBytesWritten(x => x.WriteValue(unchecked((short)0xf234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 });
AssertBytesWritten(x => x.WriteValue((int)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 });
AssertBytesWritten(x => x.WriteValue((long)0x0102030412345678), XmlBinaryNodeType.Int64Text, new byte[] { 0x78, 0x56, 0x34, 0x12, 04, 03, 02, 01 });

// Integer values should be represented using smalles possible type
AssertBytesWritten(x => x.WriteValue((long)0), XmlBinaryNodeType.ZeroText, Span<byte>.Empty);
AssertBytesWritten(x => x.WriteValue((long)1), XmlBinaryNodeType.OneText, Span<byte>.Empty);
AssertBytesWritten(x => x.WriteValue((int)0x00000078), XmlBinaryNodeType.Int8Text, new byte[] { 0x78 });
AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffffff0)), XmlBinaryNodeType.Int8Text, new byte[] { 0xf0 });
AssertBytesWritten(x => x.WriteValue((int)0x00001234), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0x12 });
AssertBytesWritten(x => x.WriteValue(unchecked((int)0xfffff234)), XmlBinaryNodeType.Int16Text, new byte[] { 0x34, 0xf2 });
AssertBytesWritten(x => x.WriteValue((long)0x12345678), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0x12 });
AssertBytesWritten(x => x.WriteValue(unchecked((long)0xfffffffff2345678)), XmlBinaryNodeType.Int32Text, new byte[] { 0x78, 0x56, 0x34, 0xf2 });

float f = 1.23456788f;
ReadOnlySpan<byte> floatBytes = new byte[] { 0x52, 0x06, 0x9e, 0x3f };
double d = 1.0 / 3.0;
ReadOnlySpan<byte> doubleBytes = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f };
Guid guid = new Guid(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 });
DateTime datetime = new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc);
Span<byte> datetimeBytes = stackalloc byte[8];
BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes, datetime.ToBinary());

AssertBytesWritten(x => x.WriteValue(f), XmlBinaryNodeType.FloatText, floatBytes);
AssertBytesWritten(x => x.WriteValue(new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b)), XmlBinaryNodeType.DecimalText,
new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1, 0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10 });
AssertBytesWritten(x => x.WriteValue(guid), XmlBinaryNodeType.GuidText, guid.ToByteArray());
AssertBytesWritten(x => x.WriteValue(new TimeSpan(0x0807060504030201)), XmlBinaryNodeType.TimeSpanText, new byte[] { 01, 02, 03, 04, 05, 06, 07, 08 });
AssertBytesWritten(x => x.WriteValue(datetime), XmlBinaryNodeType.DateTimeText, datetimeBytes);

// Double can be represented as float or int as long as no detail is lost
AssertBytesWritten(x => x.WriteValue((double)f), XmlBinaryNodeType.FloatText, floatBytes);
AssertBytesWritten(x => x.WriteValue((double)0x0100), XmlBinaryNodeType.Int16Text, new byte[] { 0x00, 0x01 });
AssertBytesWritten(x => x.WriteValue(d), XmlBinaryNodeType.DoubleText, doubleBytes);


void AssertBytesWritten(Action<XmlDictionaryWriter> action, XmlBinaryNodeType nodeType, ReadOnlySpan<byte> expected)
{
writer.WriteStartElement("a");

// Reset stream so we only compare the actual value written (including end element)
writer.Flush();
ms.Position = 0;
ms.SetLength(0);

action(writer);

writer.Flush();
ms.TryGetBuffer(out ArraySegment<byte> segement);
Assert.Equal(nodeType, (XmlBinaryNodeType)segement[0]);
AssertExtensions.SequenceEqual(expected, segement.AsSpan(1));
writer.WriteEndElement();
}
}


[Fact]
public static void BinaryWriter_Arrays()
{
using var ms = new MemoryStream();
using var writer = XmlDictionaryWriter.CreateBinaryWriter(ms);
writer.WriteStartElement("root");
int offset = 1;
int count = 2;

bool[] bools = new bool[] { false, true, false, true };
AssertBytesWritten(x => x.WriteArray(null, "a", null, bools, offset, count), XmlBinaryNodeType.BoolTextWithEndElement,
count, new byte[] { 1, 0 });

short[] shorts = new short[] { -1, 0x0102, 0x1122, -1 };
AssertBytesWritten(x => x.WriteArray(null, "a", null, shorts, offset, count), XmlBinaryNodeType.Int16TextWithEndElement,
count, new byte[] { 2, 1, 0x22, 0x11 });

int[] ints = new int[] { -1, 0x01020304, 0x11223344, -1 };
AssertBytesWritten(x => x.WriteArray(null, "a", null, ints, offset, count), XmlBinaryNodeType.Int32TextWithEndElement,
count, new byte[] { 4, 3, 2, 1, 0x44, 0x33, 0x22, 0x11 });

long[] longs = new long[] { -1, 0x0102030405060708, 0x1122334455667788, -1 };
AssertBytesWritten(x => x.WriteArray(null, "a", null, longs, offset, count), XmlBinaryNodeType.Int64TextWithEndElement,
count, new byte[] { 8, 7, 6, 5, 4, 3, 2, 1, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 });

float[] floats = new float[] { -1.0f, 1.23456788f, 1.23456788f, -1.0f };
AssertBytesWritten(x => x.WriteArray(null, "a", null, floats, offset, count), XmlBinaryNodeType.FloatTextWithEndElement,
count, new byte[] { 0x52, 0x06, 0x9e, 0x3f, 0x52, 0x06, 0x9e, 0x3f });

double[] doubles = new double[] { -1.0, 1.0 / 3.0, 1.0 / 3.0, -1.0 };
AssertBytesWritten(x => x.WriteArray(null, "a", null, doubles, offset, count), XmlBinaryNodeType.DoubleTextWithEndElement,
count, new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f,
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xd5, 0x3f });

decimal[] decimals = new[] {
new decimal(0x20212223, 0x10111213, 0x01020304, true, scale: 0x1b),
new decimal(0x50515253, 0x40414243, 0x31323334, false, scale: 0x1c)
};
AssertBytesWritten(x => x.WriteArray(null, "a", null, decimals, 0, decimals.Length), XmlBinaryNodeType.DecimalTextWithEndElement,
decimals.Length, new byte[] { 0x0, 0x0, 0x1b, 0x80, 0x4, 0x3, 0x2, 0x1,
0x23, 0x22, 0x21, 0x20, 0x13, 0x12, 0x11, 0x10,
0x0, 0x0, 0x1c, 0x00, 0x34, 0x33, 0x32, 0x31,
0x53, 0x52, 0x51, 0x50, 0x43, 0x42, 0x41, 0x40 });

DateTime[] datetimes = new[] {
new DateTime(2022, 8, 26, 12, 34, 56, DateTimeKind.Utc),
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local)
};
Span<byte> datetimeBytes = stackalloc byte[8 * datetimes.Length];
for (int i = 0; i < datetimes.Length; i++)
{
BinaryPrimitives.WriteInt64LittleEndian(datetimeBytes.Slice(8 * i), datetimes[i].ToBinary());
}
AssertBytesWritten(x => x.WriteArray(null, "a", null, datetimes, 0, datetimes.Length), XmlBinaryNodeType.DateTimeTextWithEndElement,
datetimes.Length, datetimeBytes);

TimeSpan[] timespans = new[] { new TimeSpan(0x0807060504030201), new TimeSpan(0x1817161514131211) };
AssertBytesWritten(x => x.WriteArray(null, "a", null, timespans, 0, timespans.Length), XmlBinaryNodeType.TimeSpanTextWithEndElement,
timespans.Length, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 });

Guid[] guids = new Guid[]
{
new Guid(new ReadOnlySpan<byte>(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 })),
new Guid(new ReadOnlySpan<byte>(new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 }))
};
AssertBytesWritten(x => x.WriteArray(null, "a", null, guids, 0, guids.Length), XmlBinaryNodeType.GuidTextWithEndElement,
guids.Length, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160 });

// Write more than 512 bytes in a single call to trigger different writing logic in XmlStreamNodeWriter.WriteBytes
long[] many_longs = Enumerable.Range(0x01020304, 127).Select(i => (long)i | (long)(~i << 32)).ToArray();
Span<byte> many_longBytes = stackalloc byte[8 * many_longs.Length];
for (int i = 0; i < many_longs.Length; i++)
{
BinaryPrimitives.WriteInt64LittleEndian(many_longBytes.Slice(8 * i), many_longs[i]);
}
AssertBytesWritten(x => x.WriteArray(null, "a", null, many_longs, 0, many_longs.Length), XmlBinaryNodeType.Int64TextWithEndElement,
many_longs.Length, many_longBytes);

void AssertBytesWritten(Action<XmlDictionaryWriter> action, XmlBinaryNodeType nodeType, int count, ReadOnlySpan<byte> expected)
{
// Reset stream so we only compare the actual value written (including end element)
writer.Flush();
ms.Position = 0;
ms.SetLength(0);

action(writer);

writer.Flush();
ms.TryGetBuffer(out ArraySegment<byte> segement);

var actual = segement.AsSpan();
Assert.Equal(XmlBinaryNodeType.Array, (XmlBinaryNodeType)actual[0]);
Assert.Equal(XmlBinaryNodeType.ShortElement, (XmlBinaryNodeType)actual[1]);
int elementLength = actual[2];
Assert.InRange(elementLength, 0, 0x8f); // verify count is single byte
Assert.Equal(XmlBinaryNodeType.EndElement, (XmlBinaryNodeType)actual[3 + elementLength]);

actual = actual.Slice(4 + elementLength);
// nodetype and count
Assert.Equal(nodeType, (XmlBinaryNodeType)actual[0]);
Assert.Equal(checked((sbyte)count), (sbyte)actual[1]);

AssertExtensions.SequenceEqual(expected, actual.Slice(2));
}
}

[Fact]
public static void XmlBaseWriter_WriteString()
{
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.