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

Perf | various performance improvements for SqlDataReader, TdsParser, SqlGuid and SqlBinary #2308

Closed
wants to merge 8 commits into from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

# JetBrains Rider (cross platform .NET IDE) working folder
.idea/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1573,7 +1573,7 @@ override public Stream GetStream(int i)
else
{
// Grab already read data
data = _data[i].SqlBinary.Value;
data = _data[i].ByteArray;
}

// If non-sequential then we just have a read-only MemoryStream
Expand Down Expand Up @@ -2969,7 +2969,7 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
}
else
{
byte[] value = data.IsNull ? Array.Empty<byte>() : data.SqlBinary.Value;
byte[] value = data.IsNull ? Array.Empty<byte>() : data.ByteArray;
return (T)(object)new MemoryStream(value, writable: false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5500,11 +5500,11 @@ internal static object GetNullSqlValue(SqlBuffer nullVal, SqlMetaDataPriv md, Sq
case SqlDbType.Binary:
case SqlDbType.VarBinary:
case SqlDbType.Image:
nullVal.SqlBinary = SqlBinary.Null;
nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlBinary);
break;

case SqlDbType.UniqueIdentifier:
nullVal.SqlGuid = SqlGuid.Null;
nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlGuid);
break;

case SqlDbType.Bit:
Expand Down Expand Up @@ -5932,7 +5932,11 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt
case TdsEnums.SQLUNIQUEID:
{
Debug.Assert(length == 16, "invalid length for SqlGuid type!");
#if NET8_0_OR_GREATER
value.SqlGuid = new SqlGuid(unencryptedBytes); // doesn't copy the byte array
#else
value.SqlGuid = SqlTypeWorkarounds.SqlGuidCtor(unencryptedBytes, true); // doesn't copy the byte array
#endif
break;
}

Expand All @@ -5953,7 +5957,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt
unencryptedBytes = bytes;
}

value.SqlBinary = new SqlBinary(unencryptedBytes); // doesn't copy the byte array
value.ByteArray = unencryptedBytes; // doesn't copy the byte array
break;
}

Expand Down Expand Up @@ -6146,11 +6150,7 @@ internal bool TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, T
}
else
{
#if NET7_0_OR_GREATER
value.SqlBinary = SqlBinary.WrapBytes(b);
#else
value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array
#endif
value.ByteArray = b; // doesn't copy the byte array
}
break;

Expand Down Expand Up @@ -6443,12 +6443,8 @@ internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length,
{
return false;
}
#if NET7_0_OR_GREATER
value.SqlBinary = SqlBinary.WrapBytes(b);
#else
value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true);
#endif

value.ByteArray = b; // doesn't copy byte array
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,31 @@ private struct SqlBinaryCaster
internal SqlBinaryLookalike Fake;
}
#endregion

#region Work around inability to access SqlGuid.ctor(byte[], bool)
internal static SqlGuid SqlGuidCtor(byte[] value, bool ignored)
{
// Construct a SqlGuid without allocating/copying the byte[]. This provides
// the same behavior as SqlGuid.ctor(byte[], bool).
var c = default(SqlGuidCaster);
c.Fake._value = value;
return c.Real;
}

[StructLayout(LayoutKind.Sequential)]
private struct SqlGuidLookalike
{
internal byte[] _value;
}

[StructLayout(LayoutKind.Explicit)]
private struct SqlGuidCaster
{
[FieldOffset(0)]
internal SqlGuid Real;
[FieldOffset(0)]
internal SqlGuidLookalike Fake;
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1720,7 +1720,7 @@ virtual public XmlReader GetXmlReader(int i)
if (_data[i].IsNull)
{
// A 'null' stream
return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(new byte[0], writable: false), closeInput: true, async: false);
return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(Array.Empty<byte>(), writable: false), closeInput: true, async: false);
}
else
{
Expand Down Expand Up @@ -1765,12 +1765,12 @@ override public Stream GetStream(int i)
if (_data[i].IsNull)
{
// A 'null' stream
data = new byte[0];
data = Array.Empty<byte>();
}
else
{
// Grab already read data
data = _data[i].SqlBinary.Value;
data = _data[i].ByteArray;
}

// If non-sequential then we just have a read-only MemoryStream
Expand Down Expand Up @@ -3308,7 +3308,7 @@ private T GetFieldValueFromSqlBufferInternal<T>(SqlBuffer data, _SqlMetaData met
}
else
{
byte[] value = data.IsNull ? Array.Empty<byte>() : data.SqlBinary.Value;
byte[] value = data.IsNull ? Array.Empty<byte>() : data.ByteArray;
return (T)(object)new MemoryStream(value, writable: false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6306,11 +6306,11 @@ internal static object GetNullSqlValue(
case SqlDbType.Binary:
case SqlDbType.VarBinary:
case SqlDbType.Image:
nullVal.SqlBinary = SqlBinary.Null;
nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlBinary);
break;

case SqlDbType.UniqueIdentifier:
nullVal.SqlGuid = SqlGuid.Null;
nullVal.SetToNullOfType(SqlBuffer.StorageType.SqlGuid);
break;

case SqlDbType.Bit:
Expand Down Expand Up @@ -6741,7 +6741,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt
unencryptedBytes = bytes;
}

value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(unencryptedBytes, true); // doesn't copy the byte array
value.ByteArray = unencryptedBytes; // doesn't copy the byte array
break;
}

Expand Down Expand Up @@ -6938,7 +6938,7 @@ internal bool TryReadSqlValue(SqlBuffer value,
}
else
{
value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array
value.ByteArray = b; // doesn't copy the byte array
}
break;

Expand Down Expand Up @@ -7238,8 +7238,8 @@ internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length,
{
return false;
}
value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array

value.ByteArray = b; // doesn't copy the byte array
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ internal static Stream GetStream(SmiEventSink_Default sink, ITypedGettersV3 gett
if (isDbNull)
{
// "null" stream
data = new byte[0];
data = Array.Empty<byte>();
}
else
{
Expand Down Expand Up @@ -147,15 +147,6 @@ private static void GetNullOutputParameterSmi(SmiMetaData metaData, SqlBuffer ta
{
result = DBNull.Value;
}
else if (SqlBuffer.StorageType.SqlBinary == stype)
{
// special case SqlBinary, 'cause tds parser never sets SqlBuffer to null, just to empty!
targetBuffer.SqlBinary = SqlBinary.Null;
}
else if (SqlBuffer.StorageType.SqlGuid == stype)
{
targetBuffer.SqlGuid = SqlGuid.Null;
}
else
{
targetBuffer.SetToNullOfType(stype);
Expand Down Expand Up @@ -190,7 +181,7 @@ SqlBuffer targetBuffer // destination
case SqlDbType.Image:
case SqlDbType.Timestamp:
case SqlDbType.VarBinary:
targetBuffer.SqlBinary = GetSqlBinary_Unchecked(sink, getters, ordinal);
targetBuffer.ByteArray = GetByteArray_Unchecked(sink, getters, ordinal);
break;
case SqlDbType.Bit:
targetBuffer.Boolean = GetBoolean_Unchecked(sink, getters, ordinal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ internal struct Storage
private bool _isNull;
private StorageType _type;
private Storage _value;
private object _object; // String, SqlBinary, SqlCachedBuffer, SqlGuid, SqlString, SqlXml
private object _object; // String, byte[] for SqlBinary, SqlCachedBuffer, SqlString, SqlXml

internal SqlBuffer()
{
Expand Down Expand Up @@ -179,7 +179,19 @@ internal byte[] ByteArray
get
{
ThrowIfNull();
return SqlBinary.Value;

if (StorageType.SqlBinary == _type)
{
return (byte[])_object;
}
return ((SqlBinary)SqlValue).Value; // anything else we haven't thought of goes through boxing.
}
set
{
Debug.Assert(IsEmpty, "setting value a second time?");
_object = value;
_type = StorageType.SqlBinary;
_isNull = value is null;
}
}

Expand Down Expand Up @@ -653,22 +665,16 @@ internal SqlBinary SqlBinary
{
get
{
if (StorageType.SqlBinary == _type)
if (IsNull)
{
if (IsNull)
{
return SqlBinary.Null;
}
return (SqlBinary)_object;
return SqlBinary.Null;
}
return (SqlBinary)SqlValue; // anything else we haven't thought of goes through boxing.
}
set
{
Debug.Assert(IsEmpty, "setting value a second time?");
_object = value;
_type = StorageType.SqlBinary;
_isNull = value.IsNull;

#if NET7_0_OR_GREATER
return SqlBinary.WrapBytes(ByteArray);
#else
return SqlTypeWorkarounds.SqlBinaryCtor(ByteArray, true); // doesn't copy the byte array
#endif
}
}

Expand Down Expand Up @@ -815,14 +821,17 @@ internal SqlGuid SqlGuid
}
else if (StorageType.SqlGuid == _type)
{
return IsNull ? SqlGuid.Null : (SqlGuid)_object;
return IsNull ? SqlGuid.Null : new SqlGuid(_value._guid);
}
return (SqlGuid)SqlValue; // anything else we haven't thought of goes through boxing.
}
set
{
Debug.Assert(IsEmpty, "setting value a second time?");
_object = value;
if (!value.IsNull)
{
_value._guid = value.Value;
}
_type = StorageType.SqlGuid;
_isNull = value.IsNull;
}
Expand Down
Loading