From 5561ec41b8ad3274e6cdf2bf4c9a376e7e80299d Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Fri, 14 Jul 2023 03:26:30 -0300 Subject: [PATCH 1/5] Performance: Removes marshalling from NativeToManaged conversions. - Reduce memory allocations in MarshalManagedToNative. - Convert XSQLDA / XSQLVAR (internals) to structs. - Appends _STRUCT suffix to XSQLDA / XSQLVAR to make the conversion path easier (can be removed, later). --- .../Client/Native/Marshalers/XSQLDA.cs | 8 +- .../Client/Native/Marshalers/XSQLVAR.cs | 16 +- .../Native/Marshalers/XsqldaMarshaler.cs | 167 +++++------------- .../FirebirdSql.Data.FirebirdClient.csproj | 1 + 4 files changed, 56 insertions(+), 136 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLDA.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLDA.cs index c4406a0e2..d6a56cee8 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLDA.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLDA.cs @@ -15,16 +15,12 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) -using System.Runtime.InteropServices; - namespace FirebirdSql.Data.Client.Native.Marshalers; -[StructLayout(LayoutKind.Sequential)] -internal struct XSQLDA +internal unsafe struct XSQLDA_STRUCT { public short version; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] - public string sqldaid; + public fixed byte sqldaid[8]; public int sqldabc; public short sqln; public short sqld; diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLVAR.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLVAR.cs index 3af3ff5ac..215cb0e64 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLVAR.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XSQLVAR.cs @@ -16,12 +16,10 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) using System; -using System.Runtime.InteropServices; namespace FirebirdSql.Data.Client.Native.Marshalers; -[StructLayout(LayoutKind.Sequential)] -internal class XSQLVAR +internal unsafe struct XSQLVAR_STRUCT { public short sqltype; public short sqlscale; @@ -30,15 +28,11 @@ internal class XSQLVAR public IntPtr sqldata; public IntPtr sqlind; public short sqlname_length; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] sqlname; + public fixed byte sqlname[32]; public short relname_length; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] relname; + public fixed byte relname[32]; public short ownername_length; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] ownername; + public fixed byte ownername[32]; public short aliasname_length; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] aliasname; + public fixed byte aliasname[32]; } diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs index 65b770264..aa818aac3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs @@ -17,31 +17,24 @@ using System; using System.Runtime.InteropServices; -using System.IO; using FirebirdSql.Data.Common; -using System.Threading.Tasks; namespace FirebirdSql.Data.Client.Native.Marshalers; internal static class XsqldaMarshaler { - private static int SizeOfXSQLDA = Marshal.SizeOf(); - private static int SizeOfXSQLVAR = Marshal.SizeOf(); - - public static void CleanUpNativeData(ref IntPtr pNativeData) + public unsafe static void CleanUpNativeData(ref IntPtr pNativeData) { if (pNativeData != IntPtr.Zero) { - var xsqlda = Marshal.PtrToStructure(pNativeData); + var xsqlda = *(XSQLDA_STRUCT*)pNativeData; - Marshal.DestroyStructure(pNativeData); + Marshal.DestroyStructure(pNativeData); for (var i = 0; i < xsqlda.sqln; i++) { var ptr = IntPtr.Add(pNativeData, ComputeLength(i)); - - var sqlvar = new XSQLVAR(); - MarshalXSQLVARNativeToManaged(ptr, sqlvar, true); + var sqlvar = *(XSQLVAR_STRUCT*)ptr; if (sqlvar.sqldata != IntPtr.Zero) { @@ -55,7 +48,7 @@ public static void CleanUpNativeData(ref IntPtr pNativeData) sqlvar.sqlind = IntPtr.Zero; } - Marshal.DestroyStructure(ptr); + Marshal.DestroyStructure(ptr); } Marshal.FreeHGlobal(pNativeData); @@ -64,90 +57,75 @@ public static void CleanUpNativeData(ref IntPtr pNativeData) } } - public static IntPtr MarshalManagedToNative(Charset charset, Descriptor descriptor) + public unsafe static IntPtr MarshalManagedToNative(Charset charset, Descriptor descriptor) { - var xsqlda = new XSQLDA + var xsqlda = new XSQLDA_STRUCT { version = descriptor.Version, sqln = descriptor.Count, sqld = descriptor.ActualCount }; - var xsqlvar = new XSQLVAR[descriptor.Count]; + var size = ComputeLength(xsqlda.sqln); + var ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(xsqlda, ptr, true); - for (var i = 0; i < xsqlvar.Length; i++) + var xsqlvar = new XSQLVAR_STRUCT(); + for (var i = 0; i < xsqlda.sqln; i++) { - xsqlvar[i] = new XSQLVAR - { - sqltype = descriptor[i].DataType, - sqlscale = descriptor[i].NumericScale, - sqlsubtype = descriptor[i].SubType, - sqllen = descriptor[i].Length - }; - + xsqlvar.sqltype = descriptor[i].DataType; + xsqlvar.sqlscale = descriptor[i].NumericScale; + xsqlvar.sqlsubtype = descriptor[i].SubType; + xsqlvar.sqllen = descriptor[i].Length; if (descriptor[i].HasDataType() && descriptor[i].DbDataType != DbDataType.Null) { var buffer = descriptor[i].DbValue.GetBytes(); - xsqlvar[i].sqldata = Marshal.AllocHGlobal(buffer.Length); - Marshal.Copy(buffer, 0, xsqlvar[i].sqldata, buffer.Length); + xsqlvar.sqldata = Marshal.AllocHGlobal(buffer.Length); + Marshal.Copy(buffer, 0, xsqlvar.sqldata, buffer.Length); } else { - xsqlvar[i].sqldata = Marshal.AllocHGlobal(0); + xsqlvar.sqldata = Marshal.AllocHGlobal(0); } - xsqlvar[i].sqlind = Marshal.AllocHGlobal(Marshal.SizeOf()); - Marshal.WriteInt16(xsqlvar[i].sqlind, descriptor[i].NullFlag); - - xsqlvar[i].sqlname = GetStringBuffer(charset, descriptor[i].Name); - xsqlvar[i].sqlname_length = (short)descriptor[i].Name.Length; - - xsqlvar[i].relname = GetStringBuffer(charset, descriptor[i].Relation); - xsqlvar[i].relname_length = (short)descriptor[i].Relation.Length; - - xsqlvar[i].ownername = GetStringBuffer(charset, descriptor[i].Owner); - xsqlvar[i].ownername_length = (short)descriptor[i].Owner.Length; + xsqlvar.sqlind = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.WriteInt16(xsqlvar.sqlind, descriptor[i].NullFlag); - xsqlvar[i].aliasname = GetStringBuffer(charset, descriptor[i].Alias); - xsqlvar[i].aliasname_length = (short)descriptor[i].Alias.Length; - } + fixed (char* psqlname = descriptor[i].Name) + fixed (char* prelname = descriptor[i].Relation) + fixed (char* pownername = descriptor[i].Owner) + fixed (char* paliasname = descriptor[i].Alias) + { + xsqlvar.sqlname_length = (short)descriptor[i].Name.Length; + charset.Encoding.GetBytes(psqlname, descriptor[i].Name.Length, xsqlvar.sqlname, 32); - return MarshalManagedToNative(xsqlda, xsqlvar); - } + xsqlvar.relname_length = (short)descriptor[i].Relation.Length; + charset.Encoding.GetBytes(prelname, descriptor[i].Relation.Length, xsqlvar.relname, 32); - public static IntPtr MarshalManagedToNative(XSQLDA xsqlda, XSQLVAR[] xsqlvar) - { - var size = ComputeLength(xsqlda.sqln); - var ptr = Marshal.AllocHGlobal(size); + xsqlvar.ownername_length = (short)descriptor[i].Owner.Length; + charset.Encoding.GetBytes(pownername, descriptor[i].Owner.Length, xsqlvar.ownername, 32); - Marshal.StructureToPtr(xsqlda, ptr, true); + xsqlvar.aliasname_length = (short)descriptor[i].Alias.Length; + charset.Encoding.GetBytes(paliasname, descriptor[i].Alias.Length, xsqlvar.aliasname, 32); - for (var i = 0; i < xsqlvar.Length; i++) - { - var offset = ComputeLength(i); - Marshal.StructureToPtr(xsqlvar[i], IntPtr.Add(ptr, offset), true); + var offset = ComputeLength(i); + Marshal.StructureToPtr(xsqlvar, IntPtr.Add(ptr, offset), true); + } } return ptr; } - public static Descriptor MarshalNativeToManaged(Charset charset, IntPtr pNativeData) + public unsafe static Descriptor MarshalNativeToManaged(Charset charset, IntPtr pNativeData, bool fetching = false) { - return MarshalNativeToManaged(charset, pNativeData, false); - } - - public static Descriptor MarshalNativeToManaged(Charset charset, IntPtr pNativeData, bool fetching) - { - var xsqlda = Marshal.PtrToStructure(pNativeData); + var xsqlda = *(XSQLDA_STRUCT*)pNativeData; var descriptor = new Descriptor(xsqlda.sqln) { ActualCount = xsqlda.sqld }; - var xsqlvar = new XSQLVAR(); for (var i = 0; i < xsqlda.sqln; i++) { - var ptr = IntPtr.Add(pNativeData, ComputeLength(i)); - MarshalXSQLVARNativeToManaged(ptr, xsqlvar); + var xsqlvar = *(XSQLVAR_STRUCT*)IntPtr.Add(pNativeData, ComputeLength(i)); descriptor[i].DataType = xsqlvar.sqltype; descriptor[i].NumericScale = xsqlvar.sqlscale; @@ -162,54 +140,23 @@ public static Descriptor MarshalNativeToManaged(Charset charset, IntPtr pNativeD { if (descriptor[i].NullFlag != -1) { - descriptor[i].SetValue(GetBytes(xsqlvar)); + descriptor[i].SetValue(GetBytes(ref xsqlvar)); } } - descriptor[i].Name = GetString(charset, xsqlvar.sqlname, xsqlvar.sqlname_length); - descriptor[i].Relation = GetString(charset, xsqlvar.relname, xsqlvar.relname_length); - descriptor[i].Owner = GetString(charset, xsqlvar.ownername, xsqlvar.ownername_length); - descriptor[i].Alias = GetString(charset, xsqlvar.aliasname, xsqlvar.aliasname_length); + descriptor[i].Name = charset.Encoding.GetString(xsqlvar.sqlname, xsqlvar.sqlname_length); + descriptor[i].Relation = charset.Encoding.GetString(xsqlvar.relname, xsqlvar.relname_length); + descriptor[i].Owner = charset.Encoding.GetString(xsqlvar.ownername, xsqlvar.ownername_length); + descriptor[i].Alias = charset.Encoding.GetString(xsqlvar.aliasname, xsqlvar.aliasname_length); } return descriptor; } - private static void MarshalXSQLVARNativeToManaged(IntPtr ptr, XSQLVAR xsqlvar, bool onlyPointers = false) - { - unsafe - { - using (var reader = new BinaryReader(new UnmanagedMemoryStream((byte*)ptr.ToPointer(), SizeOfXSQLVAR))) - { - if (!onlyPointers) xsqlvar.sqltype = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.sqlscale = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.sqlsubtype = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.sqllen = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - xsqlvar.sqldata = reader.ReadIntPtr(); - xsqlvar.sqlind = reader.ReadIntPtr(); - if (!onlyPointers) xsqlvar.sqlname_length = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.sqlname = reader.ReadBytes(32); else reader.BaseStream.Position += 32; - if (!onlyPointers) xsqlvar.relname_length = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.relname = reader.ReadBytes(32); else reader.BaseStream.Position += 32; - if (!onlyPointers) xsqlvar.ownername_length = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.ownername = reader.ReadBytes(32); else reader.BaseStream.Position += 32; - if (!onlyPointers) xsqlvar.aliasname_length = reader.ReadInt16(); else reader.BaseStream.Position += sizeof(short); - if (!onlyPointers) xsqlvar.aliasname = reader.ReadBytes(32); else reader.BaseStream.Position += 32; - } - } - } + private unsafe static int ComputeLength(int n) => + sizeof(XSQLDA_STRUCT) + (n * sizeof(XSQLVAR_STRUCT)) + (IntPtr.Size == 8 ? 4 : 0); - private static int ComputeLength(int n) - { - var length = (SizeOfXSQLDA + n * SizeOfXSQLVAR); - if (IntPtr.Size == 8) - { - length += 4; - } - return length; - } - - private static byte[] GetBytes(XSQLVAR xsqlvar) + private static byte[] GetBytes(ref XSQLVAR_STRUCT xsqlvar) { if (xsqlvar.sqllen == 0 || xsqlvar.sqldata == IntPtr.Zero) { @@ -256,22 +203,4 @@ private static byte[] GetBytes(XSQLVAR xsqlvar) throw TypeHelper.InvalidDataType(type); } } - - private static byte[] GetStringBuffer(Charset charset, string value) - { - var buffer = new byte[32]; - charset.GetBytes(value, 0, value.Length, buffer, 0); - return buffer; - } - - private static string GetString(Charset charset, byte[] buffer) - { - var value = charset.GetString(buffer); - return value.TrimEnd('\0', ' '); - } - - private static string GetString(Charset charset, byte[] buffer, short bufferLength) - { - return charset.GetString(buffer, 0, bufferLength); - } } diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj b/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj index afc849886..9be203b5f 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj @@ -61,6 +61,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + From de66a70e63ab99b98fb5a7c14bb420c842710e43 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Fri, 14 Jul 2023 23:38:28 -0300 Subject: [PATCH 2/5] Performance: Initial support to ReadOnlySpan. --- .../Native/Marshalers/XsqldaMarshaler.cs | 15 +- .../Common/DbField.cs | 165 ++++++++++++++++++ .../Common/TypeDecoder.cs | 22 ++- 3 files changed, 192 insertions(+), 10 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs index aa818aac3..8147396e5 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/Marshalers/XsqldaMarshaler.cs @@ -156,7 +156,7 @@ public unsafe static Descriptor MarshalNativeToManaged(Charset charset, IntPtr p private unsafe static int ComputeLength(int n) => sizeof(XSQLDA_STRUCT) + (n * sizeof(XSQLVAR_STRUCT)) + (IntPtr.Size == 8 ? 4 : 0); - private static byte[] GetBytes(ref XSQLVAR_STRUCT xsqlvar) + private unsafe static ReadOnlySpan GetBytes(ref XSQLVAR_STRUCT xsqlvar) { if (xsqlvar.sqllen == 0 || xsqlvar.sqldata == IntPtr.Zero) { @@ -168,10 +168,9 @@ private static byte[] GetBytes(ref XSQLVAR_STRUCT xsqlvar) { case IscCodes.SQL_VARYING: { - var buffer = new byte[Marshal.ReadInt16(xsqlvar.sqldata)]; - var tmp = IntPtr.Add(xsqlvar.sqldata, 2); - Marshal.Copy(tmp, buffer, 0, buffer.Length); - return buffer; + var length = Marshal.ReadInt16(xsqlvar.sqldata); + var pointer = IntPtr.Add(xsqlvar.sqldata, 2).ToPointer(); + return new ReadOnlySpan(pointer, length); } case IscCodes.SQL_TEXT: case IscCodes.SQL_SHORT: @@ -195,9 +194,9 @@ private static byte[] GetBytes(ref XSQLVAR_STRUCT xsqlvar) case IscCodes.SQL_DEC34: case IscCodes.SQL_INT128: { - var buffer = new byte[xsqlvar.sqllen]; - Marshal.Copy(xsqlvar.sqldata, buffer, 0, buffer.Length); - return buffer; + var length = xsqlvar.sqllen; + var pointer = xsqlvar.sqldata.ToPointer(); + return new ReadOnlySpan(pointer, length); } default: throw TypeHelper.InvalidDataType(type); diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs index 924991656..155d73471 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs @@ -299,6 +299,171 @@ public bool AllowDBNull() return ((DataType & 1) == 1); } + public void SetValue(ReadOnlySpan buffer) + { + if (buffer.IsEmpty || NullFlag == -1) + { + DbValue.SetValue(DBNull.Value); + } + else + { + switch (SqlType) + { + case IscCodes.SQL_TEXT: + case IscCodes.SQL_VARYING: + if (DbDataType == DbDataType.Guid) + { + DbValue.SetValue(TypeDecoder.DecodeGuid(buffer)); + } + else + { + if (Charset.IsOctetsCharset) + { + DbValue.SetValue(buffer.ToArray()); // ToDo: Remove .ToArray() -- Possible? + } + else + { + var s = Charset.Encoding.GetString(buffer); + + if ((Length % Charset.BytesPerCharacter) == 0 && + s.Length > CharCount) + { + s = s.Substring(0, CharCount); + } + + DbValue.SetValue(s); + } + } + break; + + case IscCodes.SQL_SHORT: + if (_numericScale < 0) + { + DbValue.SetValue(TypeDecoder.DecodeDecimal(BitConverter.ToInt16(buffer), _numericScale, _dataType)); + } + else + { + DbValue.SetValue(BitConverter.ToInt16(buffer)); + } + break; + + case IscCodes.SQL_LONG: + if (_numericScale < 0) + { + DbValue.SetValue(TypeDecoder.DecodeDecimal(BitConverter.ToInt32(buffer), _numericScale, _dataType)); + } + else + { + DbValue.SetValue(BitConverter.ToInt32(buffer)); + } + break; + + case IscCodes.SQL_FLOAT: + DbValue.SetValue(BitConverter.ToSingle(buffer)); + break; + + case IscCodes.SQL_DOUBLE: + case IscCodes.SQL_D_FLOAT: + DbValue.SetValue(BitConverter.ToDouble(buffer)); + break; + + case IscCodes.SQL_QUAD: + case IscCodes.SQL_INT64: + case IscCodes.SQL_BLOB: + case IscCodes.SQL_ARRAY: + if (_numericScale < 0) + { + DbValue.SetValue(TypeDecoder.DecodeDecimal(BitConverter.ToInt64(buffer), _numericScale, _dataType)); + } + else + { + DbValue.SetValue(BitConverter.ToInt64(buffer)); + } + break; + + case IscCodes.SQL_TIMESTAMP: + { + var date = TypeDecoder.DecodeDate(BitConverter.ToInt32(buffer)); + var time = TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer.Slice(4, 4))); + DbValue.SetValue(date.Add(time)); + break; + } + + case IscCodes.SQL_TYPE_TIME: + DbValue.SetValue(TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer))); + break; + + case IscCodes.SQL_TYPE_DATE: + DbValue.SetValue(TypeDecoder.DecodeDate(BitConverter.ToInt32(buffer))); + break; + + case IscCodes.SQL_BOOLEAN: + DbValue.SetValue(TypeDecoder.DecodeBoolean(buffer)); + break; + + case IscCodes.SQL_TIMESTAMP_TZ: + { + var date = TypeDecoder.DecodeDate(BitConverter.ToInt32(buffer)); + var time = TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer.Slice(4, 4))); + var tzId = BitConverter.ToUInt16(buffer.Slice(8, 2)); + var dt = DateTime.SpecifyKind(date.Add(time), DateTimeKind.Utc); + DbValue.SetValue(TypeHelper.CreateZonedDateTime(dt, tzId, null)); + break; + } + + case IscCodes.SQL_TIMESTAMP_TZ_EX: + { + var date = TypeDecoder.DecodeDate(BitConverter.ToInt32(buffer)); + var time = TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer.Slice(4, 4))); + var tzId = BitConverter.ToUInt16(buffer.Slice(8, 2)); + var offset = BitConverter.ToInt16(buffer.Slice(10, 2)); + var dt = DateTime.SpecifyKind(date.Add(time), DateTimeKind.Utc); + DbValue.SetValue(TypeHelper.CreateZonedDateTime(dt, tzId, offset)); + break; + } + + case IscCodes.SQL_TIME_TZ: + { + var time = TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer)); + var tzId = BitConverter.ToUInt16(buffer.Slice(4, 2)); + DbValue.SetValue(TypeHelper.CreateZonedTime(time, tzId, null)); + break; + } + + case IscCodes.SQL_TIME_TZ_EX: + { + var time = TypeDecoder.DecodeTime(BitConverter.ToInt32(buffer.Slice(0, 2))); + var tzId = BitConverter.ToUInt16(buffer.Slice(4, 2)); + var offset = BitConverter.ToInt16(buffer.Slice(6, 2)); + DbValue.SetValue(TypeHelper.CreateZonedTime(time, tzId, offset)); + break; + } + + case IscCodes.SQL_DEC16: + DbValue.SetValue(DecimalCodec.DecFloat16.ParseBytes(buffer.ToArray())); // ToDo: Remove .ToArray() + break; + + case IscCodes.SQL_DEC34: + DbValue.SetValue(DecimalCodec.DecFloat34.ParseBytes(buffer.ToArray())); // ToDo: Remove .ToArray() + break; + + case IscCodes.SQL_INT128: + if (_numericScale < 0) + { + DbValue.SetValue(TypeDecoder.DecodeDecimal(Int128Helper.GetInt128(buffer.ToArray()), _numericScale, _dataType)); // ToDo: Remove .ToArray() + } + else + { + DbValue.SetValue(Int128Helper.GetInt128(buffer.ToArray())); // ToDo: Remove .ToArray() + } + break; + + default: + throw TypeHelper.InvalidDataType(SqlType); + } + } + } + public void SetValue(byte[] buffer) { if (buffer == null || NullFlag == -1) diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs index f7d058520..2ec46084e 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs @@ -93,6 +93,11 @@ public static DateTime DecodeDate(int sqlDate) return (year, month, day); } + public static bool DecodeBoolean(ReadOnlySpan value) + { + return value[0] != 0; + } + public static bool DecodeBoolean(byte[] value) { return value[0] != 0; @@ -103,9 +108,22 @@ public static Guid DecodeGuid(byte[] value) var a = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value, 0)); var b = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 4)); var c = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 6)); - var d = new[] { value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15] }; - return new Guid(a, b, c, d); + return new Guid(a, b, c, value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15]); } + public static Guid DecodeGuid(ReadOnlySpan value) => + new Guid( + a: IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value)), + b: IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value.Slice(4, 2))), + c: IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value.Slice(6, 2))), + d: value[8], + e: value[9], + f: value[10], + g: value[11], + h: value[12], + i: value[13], + j: value[14], + k: value[15] + ); public static int DecodeInt32(byte[] value) { From b46e455e420f012c97e98a7ebc09fca38d13a75b Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Sat, 15 Jul 2023 02:22:38 -0300 Subject: [PATCH 3/5] Performance: Replaces BlrData class with ReadOnlyMemory/ReadOnlySpan. --- .../Managed/DataProviderStreamWrapper.cs | 14 +++++++++++ .../Managed/FirebirdNetworkHandlingWrapper.cs | 25 +++++++++++++------ .../Client/Managed/IDataProvider.cs | 4 +++ .../Client/Managed/Version10/GdsStatement.cs | 22 +++++++++++----- .../Client/Managed/Version16/GdsBatch.cs | 12 ++++----- .../Client/Managed/XdrReaderWriter.cs | 23 ++++++++++++++++- .../Common/Descriptor.cs | 23 +++++++---------- 7 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs index 35de2a9c4..81e09f812 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs @@ -15,6 +15,7 @@ //$Authors = Jiri Cincura (jiri@cincura.net) +using System; using System.IO; using System.Runtime.CompilerServices; using System.Threading; @@ -42,11 +43,24 @@ public ValueTask ReadAsync(byte[] buffer, int offset, int count, Cancellati return new ValueTask(_stream.ReadAsync(buffer, offset, count, cancellationToken)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan buffer) + { + _stream.Write(buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(byte[] buffer, int offset, int count) { _stream.Write(buffer, offset, count); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs index 28d85b36f..e8ebbda68 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -33,7 +33,7 @@ sealed class FirebirdNetworkHandlingWrapper : IDataProvider, ITracksIOFailure readonly IDataProvider _dataProvider; - readonly Queue _outputBuffer; + readonly MemoryStream _outputBuffer; readonly Queue _inputBuffer; readonly byte[] _readBuffer; @@ -48,7 +48,7 @@ public FirebirdNetworkHandlingWrapper(IDataProvider dataProvider) { _dataProvider = dataProvider; - _outputBuffer = new Queue(PreferredBufferSize); + _outputBuffer = new MemoryStream(PreferredBufferSize); _inputBuffer = new Queue(PreferredBufferSize); _readBuffer = new byte[PreferredBufferSize]; } @@ -120,22 +120,31 @@ public async ValueTask ReadAsync(byte[] buffer, int offset, int count, Canc return dataLength; } + public void Write(ReadOnlySpan buffer) + { + _outputBuffer.Write(buffer); + } + public void Write(byte[] buffer, int offset, int count) { - for (var i = offset; i < count; i++) - _outputBuffer.Enqueue(buffer[offset + i]); + _outputBuffer.Write(buffer, offset, count); } + + public async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await _outputBuffer.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + } + public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { - for (var i = offset; i < count; i++) - _outputBuffer.Enqueue(buffer[offset + i]); + _outputBuffer.Write(buffer, offset, count); return ValueTask2.CompletedTask; } public void Flush() { var buffer = _outputBuffer.ToArray(); - _outputBuffer.Clear(); + _outputBuffer.SetLength(0); var count = buffer.Length; if (_compressor != null) { @@ -160,7 +169,7 @@ public void Flush() public async ValueTask FlushAsync(CancellationToken cancellationToken = default) { var buffer = _outputBuffer.ToArray(); - _outputBuffer.Clear(); + _outputBuffer.SetLength(0); var count = buffer.Length; if (_compressor != null) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs index f15ca14d4..f9483c6f9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs @@ -15,6 +15,7 @@ //$Authors = Jiri Cincura (jiri@cincura.net) +using System; using System.Threading; using System.Threading.Tasks; @@ -25,7 +26,10 @@ interface IDataProvider int Read(byte[] buffer, int offset, int count); ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + void Write(ReadOnlySpan buffer); void Write(byte[] buffer, int offset, int count); + + ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); void Flush(); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index f920c2706..b7cc75ff1 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -399,7 +399,7 @@ public override DbValue[] Fetch() { _database.Xdr.Write(IscCodes.op_fetch); _database.Xdr.Write(_handle); - _database.Xdr.WriteBuffer(_fields.ToBlr().Data); + _database.Xdr.WriteBuffer(_fields.ToBlrSpan()); _database.Xdr.Write(0); // p_sqldata_message_number _database.Xdr.Write(_fetchSize); // p_sqldata_messages _database.Xdr.Flush(); @@ -480,7 +480,7 @@ public override async ValueTask FetchAsync(CancellationToken cancella { await _database.Xdr.WriteAsync(IscCodes.op_fetch, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteBufferAsync(_fields.ToBlr().Data, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBufferAsync(_fields.ToBlrMemory(), cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // p_sqldata_message_number await _database.Xdr.WriteAsync(_fetchSize, cancellationToken).ConfigureAwait(false); // p_sqldata_messages await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -781,7 +781,7 @@ protected virtual void SendExecuteToBuffer(int timeout, IDescriptorFiller descri if (_parameters != null) { - _database.Xdr.WriteBuffer(_parameters.ToBlr().Data); + _database.Xdr.WriteBuffer(_parameters.ToBlrSpan()); _database.Xdr.Write(0); // Message number _database.Xdr.Write(1); // Number of messages _database.Xdr.WriteBytes(parametersData, parametersData.Length); @@ -795,7 +795,12 @@ protected virtual void SendExecuteToBuffer(int timeout, IDescriptorFiller descri if (StatementType == DbStatementType.StoredProcedure) { - _database.Xdr.WriteBuffer(_fields?.ToBlr().Data); + _database.Xdr.WriteBuffer( + _fields is null + ? ReadOnlySpan.Empty + : _fields.ToBlrSpan() + ); + _database.Xdr.Write(0); // Output message number } } @@ -818,7 +823,7 @@ protected virtual async ValueTask SendExecuteToBufferAsync(int timeout, IDescrip if (_parameters != null) { - await _database.Xdr.WriteBufferAsync(_parameters.ToBlr().Data, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBufferAsync(_parameters.ToBlrMemory(), cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // Message number await _database.Xdr.WriteAsync(1, cancellationToken).ConfigureAwait(false); // Number of messages await _database.Xdr.WriteBytesAsync(parametersData, parametersData.Length, cancellationToken).ConfigureAwait(false); @@ -832,7 +837,12 @@ protected virtual async ValueTask SendExecuteToBufferAsync(int timeout, IDescrip if (StatementType == DbStatementType.StoredProcedure) { - await _database.Xdr.WriteBufferAsync(_fields?.ToBlr().Data, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBufferAsync( + _fields is null + ? ReadOnlyMemory.Empty + : _fields.ToBlrMemory(), + cancellationToken + ).ConfigureAwait(false); await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // Output message number } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs index e0f68b88f..9bf2b7da3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs @@ -43,9 +43,9 @@ public override ExecuteResultItem[] Execute(int count, IDescriptorFiller descrip Database.Xdr.Write(IscCodes.op_batch_create); Database.Xdr.Write(_statement.Handle); // p_batch_statement - var blr = _statement.Parameters.ToBlr(); - Database.Xdr.WriteBuffer(blr.Data); // p_batch_blr - Database.Xdr.Write(blr.Length); // p_batch_msglen + var blr = _statement.Parameters.ToBlrSpan(out var blrMsgLen); + Database.Xdr.WriteBuffer(blr); // p_batch_blr + Database.Xdr.Write(blrMsgLen); // p_batch_msglen var pb = _statement.CreateBatchParameterBuffer(); if (_statement.ReturnRecordsAffected) { @@ -96,9 +96,9 @@ public override async ValueTask ExecuteAsync(int count, IDe await Database.Xdr.WriteAsync(IscCodes.op_batch_create, cancellationToken).ConfigureAwait(false); await Database.Xdr.WriteAsync(_statement.Handle, cancellationToken).ConfigureAwait(false); // p_batch_statement - var blr = _statement.Parameters.ToBlr(); - await Database.Xdr.WriteBufferAsync(blr.Data, cancellationToken).ConfigureAwait(false); // p_batch_blr - await Database.Xdr.WriteAsync(blr.Length, cancellationToken).ConfigureAwait(false); // p_batch_msglen + var blr = _statement.Parameters.ToBlrMemory(out var blrMsgLen); + await Database.Xdr.WriteBufferAsync(blr, cancellationToken).ConfigureAwait(false); // p_batch_blr + await Database.Xdr.WriteAsync(blrMsgLen, cancellationToken).ConfigureAwait(false); // p_batch_msglen var pb = _statement.CreateBatchParameterBuffer(); if (_statement.ReturnRecordsAffected) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index eeb4c322c..3bad75814 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -521,11 +521,22 @@ public void WriteBuffer(byte[] buffer) { WriteBuffer(buffer, buffer?.Length ?? 0); } + public ValueTask WriteBufferAsync(byte[] buffer, CancellationToken cancellationToken = default) { return WriteBufferAsync(buffer, buffer?.Length ?? 0, cancellationToken); } + public void WriteBuffer(ReadOnlySpan buffer) + { + Write(buffer.Length); + if (buffer.Length > 0) + { + _dataProvider.Write(buffer); + WritePad((4 - buffer.Length) & 3); + } + } + public void WriteBuffer(byte[] buffer, int length) { Write(length); @@ -535,6 +546,17 @@ public void WriteBuffer(byte[] buffer, int length) WritePad((4 - length) & 3); } } + + public async Task WriteBufferAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + await WriteAsync(buffer.Length, cancellationToken).ConfigureAwait(false); + if (buffer.Length > 0) + { + await _dataProvider.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - buffer.Length) & 3, cancellationToken).ConfigureAwait(false); + } + } + public async ValueTask WriteBufferAsync(byte[] buffer, int length, CancellationToken cancellationToken = default) { await WriteAsync(length, cancellationToken).ConfigureAwait(false); @@ -838,6 +860,5 @@ ValueTask WriteFillAsync(int length, CancellationToken cancellationToken = defau { return _dataProvider.WriteAsync(FillArray, 0, length, cancellationToken); } - #endregion } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs index beab8c576..f6584f4b7 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs @@ -98,23 +98,13 @@ public void ResetValues() } } - internal sealed class BlrData - { - public byte[] Data { get; } - public int Length { get; } + public ReadOnlyMemory ToBlrMemory() => ToBlrMemory(out int _); - public BlrData(byte[] data, int length) - { - Data = data; - Length = length; - } - } - public BlrData ToBlr() + public ReadOnlyMemory ToBlrMemory(out int length) { + length = 0; using (var blr = new MemoryStream(256)) { - var length = 0; - blr.WriteByte(IscCodes.blr_version5); blr.WriteByte(IscCodes.blr_begin); blr.WriteByte(IscCodes.blr_message); @@ -292,9 +282,14 @@ public BlrData ToBlr() blr.WriteByte(IscCodes.blr_end); blr.WriteByte(IscCodes.blr_eoc); - return new BlrData(blr.ToArray(), length); + var buf = blr.GetBuffer(); + return new ReadOnlyMemory(buf, 0, buf.Length); } } + public ReadOnlySpan ToBlrSpan() => ToBlrMemory(out int _).Span; + + public ReadOnlySpan ToBlrSpan(out int length) => ToBlrMemory(out length).Span; + #endregion } From e03a2b477bfbcf655a0e6da5a706e9a29ae0c17a Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Fri, 25 Oct 2024 16:00:03 -0300 Subject: [PATCH 4/5] Removes net48 and netstandard2.0 (no ReadOnlySpan support). --- .../EntityFramework.Firebird.csproj | 6 +----- .../FirebirdSql.Data.FirebirdClient.csproj | 12 +----------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj b/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj index 7a028340b..0f95ef32c 100644 --- a/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj +++ b/src/EntityFramework.Firebird/EntityFramework.Firebird.csproj @@ -1,6 +1,6 @@  - net48;netstandard2.1 + netstandard2.1 EntityFramework.Firebird EntityFramework.Firebird true @@ -48,10 +48,6 @@ - - - - diff --git a/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj b/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj index 9be203b5f..ce601f412 100644 --- a/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj +++ b/src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj @@ -1,6 +1,6 @@  - net48;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0 + netstandard2.1;net6.0;net7.0;net8.0 FirebirdSql.Data.FirebirdClient FirebirdSql.Data true @@ -38,18 +38,8 @@ - - - - - - - - - - From b4d64adc2a686bdcc8a7b61c19c49b6904e78089 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Fri, 25 Oct 2024 16:19:28 -0300 Subject: [PATCH 5/5] Removes versionEF6 (net48). --- build.ps1 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index 9a321609d..e2f5cbb88 100644 --- a/build.ps1 +++ b/build.ps1 @@ -10,7 +10,6 @@ $baseDir = Split-Path -Parent $PSCommandPath $outDir = "$baseDir\out" $versionProvider = '' $versionEFCore = '' -$versionEF6 = '' function Clean() { if (Test-Path $outDir) { @@ -39,17 +38,14 @@ function Versions() { } $script:versionProvider = v $baseDir\src\FirebirdSql.Data.FirebirdClient\bin\$Configuration\net8.0\FirebirdSql.Data.FirebirdClient.dll $script:versionEFCore = v $baseDir\src\FirebirdSql.EntityFrameworkCore.Firebird\bin\$Configuration\net8.0\FirebirdSql.EntityFrameworkCore.Firebird.dll - $script:versionEF6 = v $baseDir\src\EntityFramework.Firebird\bin\$Configuration\net48\EntityFramework.Firebird.dll } function NuGets() { cp $baseDir\src\FirebirdSql.Data.FirebirdClient\bin\$Configuration\FirebirdSql.Data.FirebirdClient.$versionProvider.nupkg $outDir cp $baseDir\src\FirebirdSql.EntityFrameworkCore.Firebird\bin\$Configuration\FirebirdSql.EntityFrameworkCore.Firebird.$versionEFCore.nupkg $outDir - cp $baseDir\src\EntityFramework.Firebird\bin\$Configuration\EntityFramework.Firebird.$versionEF6.nupkg $outDir cp $baseDir\src\FirebirdSql.Data.FirebirdClient\bin\$Configuration\FirebirdSql.Data.FirebirdClient.$versionProvider.snupkg $outDir cp $baseDir\src\FirebirdSql.EntityFrameworkCore.Firebird\bin\$Configuration\FirebirdSql.EntityFrameworkCore.Firebird.$versionEFCore.snupkg $outDir - cp $baseDir\src\EntityFramework.Firebird\bin\$Configuration\EntityFramework.Firebird.$versionEF6.snupkg $outDir } Clean