Skip to content

Commit

Permalink
Faster parsing for multiple rows
Browse files Browse the repository at this point in the history
When result-set contains multiple rows, in order to get field value, the columns details are checked for each field parsing.

This PR permit to set parsing method once for the result-set.
This doesn't change much in terms of performance, but still not negligible.

Performance results:

```
[Benchmark]
public async Task getInt64()
{
	using var cmd = Connection.CreateCommand();
	cmd.CommandText = "SELECT * FROM seq_1_to_100000";
	using var reader = await cmd.ExecuteReaderAsync();
	long total = 0;
	do
	{
		while (await reader.ReadAsync())
		{
			total += reader.GetInt64(0);
		}
	} while (await reader.NextResultAsync());
}
```

Initial results:

```
|   Method |        Library |     Mean |    Error |
|--------- |--------------- |---------:|---------:|
| getInt64 | MySqlConnector | 17.91 ms | 0.149 ms |
```

PR results:

```
|   Method |        Library |     Mean |    Error |
|--------- |--------------- |---------:|---------:|
| getInt64 | MySqlConnector | 17.59 ms | 0.125 ms |
```
  • Loading branch information
rusher committed Jun 16, 2023
1 parent f53882e commit 2910f6d
Show file tree
Hide file tree
Showing 44 changed files with 1,290 additions and 356 deletions.
20 changes: 20 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryBooleanColumnReader.cs
@@ -0,0 +1,20 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryBooleanColumnReader : IColumnReader
{
internal static BinaryBooleanColumnReader Instance { get; } = new BinaryBooleanColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return data[0] != 0;
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return data[0] != 0 ? 1 : 0;
}
}
74 changes: 74 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryDateTimeColumnReader.cs
@@ -0,0 +1,74 @@
namespace MySqlConnector.ColumnReaders;

using System;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using System.Text;
using MySqlConnector.Core;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryDateTimeColumnReader : IColumnReader
{
private bool allowZeroDateTime;
private bool convertZeroDateTime;
private DateTimeKind dateTimeKind;
internal BinaryDateTimeColumnReader(MySqlConnection connection)
{
this.allowZeroDateTime = connection.AllowZeroDateTime;
this.convertZeroDateTime = connection.ConvertZeroDateTime;
this.dateTimeKind = connection.DateTimeKind;
}

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
if (data.Length == 0)
{
if (this.convertZeroDateTime)
return DateTime.MinValue;
if (this.allowZeroDateTime)
return default(MySqlDateTime);
throw new InvalidCastException("Unable to convert MySQL date/time to System.DateTime.");
}

int year = data[0] + data[1] * 256;
int month = data[2];
int day = data[3];

int hour, minute, second;
if (data.Length <= 4)
{
hour = 0;
minute = 0;
second = 0;
}
else
{
hour = data[4];
minute = data[5];
second = data[6];
}

var microseconds = data.Length <= 7 ? 0 : MemoryMarshal.Read<int>(data[7..]);

try
{
return this.allowZeroDateTime ? (object) new MySqlDateTime(year, month, day, hour, minute, second, microseconds) :
#if NET7_0_OR_GREATER
new DateTime(year, month, day, hour, minute, second, microseconds / 1000, microseconds % 1000, this.dateTimeKind);
#else
new DateTime(year, month, day, hour, minute, second, microseconds / 1000, this.dateTimeKind).AddTicks(microseconds % 1000 * 10);
#endif
}
catch (Exception ex)
{
throw new FormatException($"Couldn't interpret value as a valid DateTime: {Encoding.UTF8.GetString(data)}", ex);
}
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
throw new InvalidCastException($"Can't convert {columnDefinition.ColumnType} to Int32");
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryDoubleColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryDoubleColumnReader : IColumnReader
{
internal static BinaryDoubleColumnReader Instance { get; } = new BinaryDoubleColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<double>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
throw new InvalidCastException($"Can't convert {columnDefinition.ColumnType} to Int32");
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryFloatColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryFloatColumnReader : IColumnReader
{
internal static BinaryFloatColumnReader Instance { get; } = new BinaryFloatColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<float>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
throw new InvalidCastException($"Can't convert {columnDefinition.ColumnType} to Int32");
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinarySignedInt16ColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinarySignedInt16ColumnReader : IColumnReader
{
internal static BinarySignedInt16ColumnReader Instance { get; } = new BinarySignedInt16ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<short>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) MemoryMarshal.Read<short>(data);
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinarySignedInt32ColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinarySignedInt32ColumnReader : IColumnReader
{
internal static BinarySignedInt32ColumnReader Instance { get; } = new BinarySignedInt32ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<int>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<int>(data);
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinarySignedInt64ColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinarySignedInt64ColumnReader : IColumnReader
{
internal static BinarySignedInt64ColumnReader Instance { get; } = new BinarySignedInt64ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<long>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return checked((int) MemoryMarshal.Read<long>(data));
}
}
20 changes: 20 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinarySignedInt8ColumnReader.cs
@@ -0,0 +1,20 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinarySignedInt8ColumnReader : IColumnReader
{
internal static BinarySignedInt8ColumnReader Instance { get; } = new BinarySignedInt8ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (sbyte) data[0];
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) (sbyte) data[0];
}
}
44 changes: 44 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryTimeColumnReader.cs
@@ -0,0 +1,44 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryTimeColumnReader : IColumnReader
{
internal static BinaryTimeColumnReader Instance { get; } = new BinaryTimeColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
if (data.Length == 0)
return TimeSpan.Zero;

var isNegative = data[0];
var days = MemoryMarshal.Read<int>(data[1..]);
var hours = (int) data[5];
var minutes = (int) data[6];
var seconds = (int) data[7];
var microseconds = data.Length == 8 ? 0 : MemoryMarshal.Read<int>(data[8..]);

if (isNegative != 0)
{
days = -days;
hours = -hours;
minutes = -minutes;
seconds = -seconds;
microseconds = -microseconds;
}

#if NET7_0_OR_GREATER
return new TimeSpan(days, hours, minutes, seconds, microseconds / 1000, microseconds % 1000);
#else
return new TimeSpan(days, hours, minutes, seconds) + TimeSpan.FromTicks(microseconds * 10);
#endif
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
throw new InvalidCastException($"Can't convert {columnDefinition.ColumnType} to Int32");
}
}
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryUnsignedInt16ColumnReader : IColumnReader
{
internal static BinaryUnsignedInt16ColumnReader Instance { get; } = new BinaryUnsignedInt16ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<ushort>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) MemoryMarshal.Read<ushort>(data);
}
}
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryUnsignedInt32ColumnReader : IColumnReader
{
internal static BinaryUnsignedInt32ColumnReader Instance { get; } = new BinaryUnsignedInt32ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<uint>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return checked((int) MemoryMarshal.Read<uint>(data));
}
}
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryUnsignedInt64ColumnReader : IColumnReader
{
internal static BinaryUnsignedInt64ColumnReader Instance { get; } = new BinaryUnsignedInt64ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return MemoryMarshal.Read<ulong>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return checked((int) MemoryMarshal.Read<ulong>(data));
}
}
20 changes: 20 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryUnsignedInt8ColumnReader.cs
@@ -0,0 +1,20 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryUnsignedInt8ColumnReader : IColumnReader
{
internal static BinaryUnsignedInt8ColumnReader Instance { get; } = new BinaryUnsignedInt8ColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (byte) data[0];
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) (byte) data[0];
}
}
21 changes: 21 additions & 0 deletions src/MySqlConnector/ColumnReaders/BinaryYearColumnReader.cs
@@ -0,0 +1,21 @@
namespace MySqlConnector.ColumnReaders;
using System.Buffers.Text;
using System.Runtime.InteropServices;
using MySqlConnector.Protocol.Payloads;
using MySqlConnector.Protocol.Serialization;
using MySqlConnector.Utilities;

internal sealed class BinaryYearColumnReader : IColumnReader
{
internal static BinaryYearColumnReader Instance { get; } = new BinaryYearColumnReader();

public object ReadValue(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) MemoryMarshal.Read<short>(data);
}

public int ReadInt32(ReadOnlySpan<byte> data, ColumnDefinitionPayload columnDefinition)
{
return (int) MemoryMarshal.Read<short>(data);
}
}

0 comments on commit 2910f6d

Please sign in to comment.