Skip to content

Commit

Permalink
Merge pull request #245 from bgrainger/performance
Browse files Browse the repository at this point in the history
Improve performance by reducing allocations. One benchmark scenario now allocates 1/10 as much, and runs almost 2x faster.
  • Loading branch information
bgrainger authored Apr 23, 2017
2 parents 138f436 + 409b060 commit 8ee7128
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 180 deletions.
15 changes: 14 additions & 1 deletion src/MySqlConnector/ByteArrayReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace MySql.Data
{
internal sealed class ByteArrayReader
internal struct ByteArrayReader
{
public ByteArrayReader(byte[] buffer, int offset, int length)
{
Expand Down Expand Up @@ -118,6 +118,8 @@ public ulong ReadLengthEncodedInteger()
byte encodedLength = m_buffer[m_offset++];
switch (encodedLength)
{
case 0xFB:
throw new FormatException("Length-encoded integer cannot have 0xFB prefix byte.");
case 0xFC:
return ReadFixedLengthUInt32(2);
case 0xFD:
Expand All @@ -131,6 +133,17 @@ public ulong ReadLengthEncodedInteger()
}
}

public int ReadLengthEncodedIntegerOrNull()
{
if (m_buffer[m_offset] == 0xFB)
{
// "NULL is sent as 0xfb" (https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-ProtocolText::ResultsetRow)
m_offset++;
return -1;
}
return checked((int) ReadLengthEncodedInteger());
}

public ArraySegment<byte> ReadLengthEncodedByteString()
{
var length = checked((int) ReadLengthEncodedInteger());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public virtual async Task<DbDataReader> ExecuteReaderAsync(string commandText, M
CommandBehavior behavior, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (cancellationToken.Register(m_command.Cancel))
using (m_command.RegisterCancel(cancellationToken))
{
m_command.Connection.Session.StartQuerying(m_command);
m_command.LastInsertedId = -1;
Expand Down
71 changes: 46 additions & 25 deletions src/MySqlConnector/MySqlClient/MySqlCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,30 +114,23 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) =>
public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) =>
ExecuteNonQueryAsync(Connection.AsyncIOBehavior, cancellationToken);

internal async Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
{
VerifyValid();
return await m_commandExecutor.ExecuteNonQueryAsync(CommandText, m_parameterCollection, ioBehavior, cancellationToken).ConfigureAwait(false);
}
internal Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) =>
!IsValid(out var exception) ? Utility.TaskFromException<int>(exception) :
m_commandExecutor.ExecuteNonQueryAsync(CommandText, m_parameterCollection, ioBehavior, cancellationToken);

public override Task<object> ExecuteScalarAsync(CancellationToken cancellationToken) =>
ExecuteScalarAsync(Connection.AsyncIOBehavior, cancellationToken);

internal async Task<object> ExecuteScalarAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
{
VerifyValid();
return await m_commandExecutor.ExecuteScalarAsync(CommandText, m_parameterCollection, ioBehavior, cancellationToken).ConfigureAwait(false);
}
internal Task<object> ExecuteScalarAsync(IOBehavior ioBehavior, CancellationToken cancellationToken) =>
!IsValid(out var exception) ? Utility.TaskFromException<object>(exception) :
m_commandExecutor.ExecuteScalarAsync(CommandText, m_parameterCollection, ioBehavior, cancellationToken);

protected override Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) =>
ExecuteReaderAsync(behavior, Connection.AsyncIOBehavior, cancellationToken);

internal async Task<DbDataReader> ExecuteReaderAsync(CommandBehavior behavior, IOBehavior ioBehavior,
CancellationToken cancellationToken)
{
VerifyValid();
return await m_commandExecutor.ExecuteReaderAsync(CommandText, m_parameterCollection, behavior, ioBehavior, cancellationToken).ConfigureAwait(false);
}
internal Task<DbDataReader> ExecuteReaderAsync(CommandBehavior behavior, IOBehavior ioBehavior, CancellationToken cancellationToken) =>
!IsValid(out var exception) ? Utility.TaskFromException<DbDataReader>(exception) :
m_commandExecutor.ExecuteReaderAsync(CommandText, m_parameterCollection, behavior, ioBehavior, cancellationToken);

protected override void Dispose(bool disposing)
{
Expand All @@ -156,6 +149,23 @@ protected override void Dispose(bool disposing)

internal new MySqlConnection Connection => (MySqlConnection) DbConnection;

/// <summary>
/// Registers <see cref="Cancel"/> as a callback with <paramref name="token"/> if cancellation is supported.
/// </summary>
/// <param name="token">The <see cref="CancellationToken"/>.</param>
/// <returns>An object that must be disposed to revoke the cancellation registration.</returns>
/// <remarks>This method is more efficient than calling <code>token.Register(Command.Cancel)</code> because it avoids
/// unnecessary allocations.</remarks>
internal IDisposable RegisterCancel(CancellationToken token)
{
if (!token.CanBeCanceled)
return null;

if (m_cancelAction == null)
m_cancelAction = Cancel;
return token.Register(m_cancelAction);
}

private void VerifyNotDisposed()
{
if (m_parameterCollection == null)
Expand All @@ -164,21 +174,32 @@ private void VerifyNotDisposed()

private void VerifyValid()
{
VerifyNotDisposed();
if (DbConnection == null)
throw new InvalidOperationException("Connection property must be non-null.");
if (DbConnection.State != ConnectionState.Open && DbConnection.State != ConnectionState.Connecting)
throw new InvalidOperationException("Connection must be Open; current state is {0}".FormatInvariant(DbConnection.State));
if (DbTransaction != Connection.CurrentTransaction)
throw new InvalidOperationException("The transaction associated with this command is not the connection's active transaction.");
if (string.IsNullOrWhiteSpace(CommandText))
throw new InvalidOperationException("CommandText must be specified");
Exception exception;
if (!IsValid(out exception))
throw exception;
}

private bool IsValid(out Exception exception)
{
exception = null;
if (m_parameterCollection == null)
exception = new ObjectDisposedException(GetType().Name);
else if (DbConnection == null)
exception = new InvalidOperationException("Connection property must be non-null.");
else if (DbConnection.State != ConnectionState.Open && DbConnection.State != ConnectionState.Connecting)
exception = new InvalidOperationException("Connection must be Open; current state is {0}".FormatInvariant(DbConnection.State));
else if (DbTransaction != Connection.CurrentTransaction)
exception = new InvalidOperationException("The transaction associated with this command is not the connection's active transaction.");
else if (string.IsNullOrWhiteSpace(CommandText))
exception = new InvalidOperationException("CommandText must be specified");
return exception == null;
}

internal void ReaderClosed() => (m_commandExecutor as StoredProcedureCommandExecutor)?.SetParams();

MySqlParameterCollection m_parameterCollection;
CommandType m_commandType;
ICommandExecutor m_commandExecutor;
Action m_cancelAction;
}
}
2 changes: 1 addition & 1 deletion src/MySqlConnector/MySqlClient/MySqlDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private ValueTask<ResultSet> ScanResultSetAsync(IOBehavior ioBehavior, ResultSet

private async Task<ResultSet> ScanResultSetAsyncAwaited(IOBehavior ioBehavior, ResultSet resultSet, CancellationToken cancellationToken)
{
using (cancellationToken.Register(Command.Cancel))
using (Command.RegisterCancel(cancellationToken))
{
try
{
Expand Down
2 changes: 1 addition & 1 deletion src/MySqlConnector/MySqlClient/MySqlStatementPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public MySqlStatementPreparer(string commandText, MySqlParameterCollection param

public ArraySegment<byte> ParseAndBindParameters()
{
using (var stream = new MemoryStream(m_commandText.Length))
using (var stream = new MemoryStream(m_commandText.Length + 1))
using (var writer = new BinaryWriter(stream, Encoding.UTF8))
{
writer.Write((byte) CommandKind.Query);
Expand Down
22 changes: 2 additions & 20 deletions src/MySqlConnector/MySqlClient/Results/ResultSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private ValueTask<Row> ScanRowAsync(IOBehavior ioBehavior, Row row, Cancellation
if (BufferState == ResultSetState.HasMoreData || BufferState == ResultSetState.NoMoreData || BufferState == ResultSetState.None)
return new ValueTask<Row>((Row)null);

using (cancellationToken.Register(Command.Cancel))
using (Command.RegisterCancel(cancellationToken))
{
var payloadValueTask = Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None);
return payloadValueTask.IsCompletedSuccessfully
Expand Down Expand Up @@ -197,7 +197,7 @@ Row ScanRowAsyncRemainder(PayloadData payload)
var reader = new ByteArrayReader(payload.ArraySegment);
for (var column = 0; column < m_dataOffsets.Length; column++)
{
var length = checked((int) ReadFieldLength(reader));
var length = reader.ReadLengthEncodedIntegerOrNull();
m_dataLengths[column] = length == -1 ? 0 : length;
m_dataOffsets[column] = length == -1 ? -1 : reader.Offset;
reader.Offset += m_dataLengths[column];
Expand All @@ -211,24 +211,6 @@ Row ScanRowAsyncRemainder(PayloadData payload)
}
}

private static long ReadFieldLength(ByteArrayReader reader)
{
var leadByte = reader.ReadByte();
switch (leadByte)
{
case 0xFB:
return -1;
case 0xFC:
return reader.ReadFixedLengthUInt32(2);
case 0xFD:
return reader.ReadFixedLengthUInt32(3);
case 0xFE:
return checked((long) reader.ReadFixedLengthUInt64(8));
default:
return leadByte;
}
}

public string GetName(int ordinal)
{
if (ColumnDefinitions == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public IByteHandler ByteHandler
}

public ValueTask<ArraySegment<byte>> ReadPayloadAsync(ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior) =>
ProtocolUtility.ReadPayloadAsync(m_bufferedByteReader, new CompressedByteHandler(this, protocolErrorBehavior), () => default(int?), default(ArraySegment<byte>), protocolErrorBehavior, ioBehavior);
ProtocolUtility.ReadPayloadAsync(m_bufferedByteReader, new CompressedByteHandler(this, protocolErrorBehavior), () => -1, default(ArraySegment<byte>), protocolErrorBehavior, ioBehavior);

public ValueTask<int> WritePayloadAsync(ArraySegment<byte> payload, IOBehavior ioBehavior)
{
Expand Down
Loading

0 comments on commit 8ee7128

Please sign in to comment.