Skip to content

Commit

Permalink
Eliminate temporary single-element IMySqlCommand array allocation.
Browse files Browse the repository at this point in the history
Use CommandListPosition directly to store the potential list of commands. This optimizes the common case of executing a single MySqlCommand.
  • Loading branch information
bgrainger committed Aug 15, 2023
1 parent e4519e3 commit 7c54397
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 22 deletions.
10 changes: 5 additions & 5 deletions src/MySqlConnector/Core/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ namespace MySqlConnector.Core;

internal static class CommandExecutor
{
public static async ValueTask<MySqlDataReader> ExecuteReaderAsync(IReadOnlyList<IMySqlCommand> commands, ICommandPayloadCreator payloadCreator, CommandBehavior behavior, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
public static async ValueTask<MySqlDataReader> ExecuteReaderAsync(CommandListPosition commandListPosition, ICommandPayloadCreator payloadCreator, CommandBehavior behavior, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var commandListPosition = new CommandListPosition(commands);
var command = commands[0];
var command = commandListPosition.CommandAt(0);

// pre-requisite: Connection is non-null must be checked before calling this method
var connection = command.Connection!;

Log.CommandExecutorExecuteReader(command.Logger, connection.Session.Id, ioBehavior, commands.Count);
Log.CommandExecutorExecuteReader(command.Logger, connection.Session.Id, ioBehavior, commandListPosition.CommandCount);

Dictionary<string, CachedProcedure?>? cachedProcedures = null;
foreach (var command2 in commands)
for (var commandIndex = 0; commandIndex < commandListPosition.CommandCount; commandIndex++)
{
var command2 = commandListPosition.CommandAt(commandIndex);
if (command2.CommandType == CommandType.StoredProcedure)
{
cachedProcedures ??= new();
Expand Down
27 changes: 23 additions & 4 deletions src/MySqlConnector/Core/CommandListPosition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,37 @@ namespace MySqlConnector.Core;
/// </summary>
internal struct CommandListPosition
{
public CommandListPosition(IReadOnlyList<IMySqlCommand> commands)
public CommandListPosition(object commands)
{
Commands = commands;
m_commands = commands;
CommandCount = commands switch
{
MySqlCommand _ => 1,
IReadOnlyList<MySqlBatchCommand> list => list.Count,
_ => 0,
};
PreparedStatements = null;
CommandIndex = 0;
PreparedStatementIndex = 0;
}

public readonly IMySqlCommand CommandAt(int index) =>
m_commands switch
{
MySqlCommand command when index is 0 => command,
IReadOnlyList<MySqlBatchCommand> list => list[index],
_ => throw new ArgumentOutOfRangeException(nameof(index)),
};

/// <summary>
/// The commands in this list; either a singular <see cref="MySqlCommand"/> or a <see cref="IReadOnlyList{MySqlBatchCommand}"/>.
/// </summary>
private readonly object m_commands;

/// <summary>
/// The commands in the list.
/// The number of commands in the list.
/// </summary>
public IReadOnlyList<IMySqlCommand> Commands { get; }
public readonly int CommandCount;

/// <summary>
/// Associated prepared statements of commands
Expand Down
12 changes: 6 additions & 6 deletions src/MySqlConnector/Core/ConcatenatedCommandPayloadCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ internal sealed class ConcatenatedCommandPayloadCreator : ICommandPayloadCreator

public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer, bool appendSemicolon)
{
if (commandListPosition.CommandIndex == commandListPosition.Commands.Count)
if (commandListPosition.CommandIndex == commandListPosition.CommandCount)
return false;

writer.Write((byte) CommandKind.Query);

// ConcatenatedCommandPayloadCreator is only used by MySqlBatch, and MySqlBatchCommand doesn't expose attributes,
// so just write an empty attribute set if the server needs it.
if (commandListPosition.Commands[commandListPosition.CommandIndex].Connection!.Session.SupportsQueryAttributes)
if (commandListPosition.CommandAt(commandListPosition.CommandIndex).Connection!.Session.SupportsQueryAttributes)
{
// attribute count
writer.WriteLengthEncodedInteger(0);
Expand All @@ -29,15 +29,15 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
bool isComplete;
do
{
var command = commandListPosition.Commands[commandListPosition.CommandIndex];
var command = commandListPosition.CommandAt(commandListPosition.CommandIndex);
Log.PreparingCommandPayload(command.Logger, command.Connection!.Session.Id, command.CommandText!);

isComplete = SingleCommandPayloadCreator.WriteQueryPayload(command, cachedProcedures, writer,
commandListPosition.CommandIndex < commandListPosition.Commands.Count - 1 || appendSemicolon,
commandListPosition.CommandIndex < commandListPosition.CommandCount - 1 || appendSemicolon,
commandListPosition.CommandIndex == 0,
commandListPosition.CommandIndex == commandListPosition.Commands.Count - 1);
commandListPosition.CommandIndex == commandListPosition.CommandCount - 1);
commandListPosition.CommandIndex++;
} while (commandListPosition.CommandIndex < commandListPosition.Commands.Count && isComplete);
} while (commandListPosition.CommandIndex < commandListPosition.CommandCount && isComplete);

return true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MySqlConnector/Core/SingleCommandPayloadCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ internal sealed class SingleCommandPayloadCreator : ICommandPayloadCreator

public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDictionary<string, CachedProcedure?> cachedProcedures, ByteBufferWriter writer, bool appendSemicolon)
{
if (commandListPosition.CommandIndex == commandListPosition.Commands.Count)
if (commandListPosition.CommandIndex == commandListPosition.CommandCount)
return false;

var command = commandListPosition.Commands[commandListPosition.CommandIndex];
var command = commandListPosition.CommandAt(commandListPosition.CommandIndex);
commandListPosition.PreparedStatements = command.TryGetPreparedStatements();
if (commandListPosition.PreparedStatements is null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/MySqlConnector/MySqlBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private ValueTask<MySqlDataReader> ExecuteReaderAsync(CommandBehavior behavior,
var payloadCreator = Connection!.Session.SupportsComMulti ? BatchedCommandPayloadCreator.Instance :
IsPrepared ? SingleCommandPayloadCreator.Instance :
ConcatenatedCommandPayloadCreator.Instance;
return CommandExecutor.ExecuteReaderAsync(BatchCommands!.Commands, payloadCreator, behavior, default, ioBehavior, cancellationToken);
return CommandExecutor.ExecuteReaderAsync(new(BatchCommands!.Commands), payloadCreator, behavior, default, ioBehavior, cancellationToken);
}

#if NET6_0_OR_GREATER
Expand Down
2 changes: 1 addition & 1 deletion src/MySqlConnector/MySqlCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ internal ValueTask<MySqlDataReader> ExecuteReaderNoResetTimeoutAsync(CommandBeha
var activity = NoActivity ? null : Connection!.Session.StartActivity(ActivitySourceHelper.ExecuteActivityName,
ActivitySourceHelper.DatabaseStatementTagName, CommandText);
m_commandBehavior = behavior;
return CommandExecutor.ExecuteReaderAsync(new IMySqlCommand[] { this }, SingleCommandPayloadCreator.Instance, behavior, activity, ioBehavior, cancellationToken);
return CommandExecutor.ExecuteReaderAsync(new(this), SingleCommandPayloadCreator.Instance, behavior, activity, ioBehavior, cancellationToken);
}

public MySqlCommand Clone() => new(this);
Expand Down
6 changes: 3 additions & 3 deletions src/MySqlConnector/MySqlDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ internal async Task<bool> NextResultAsync(IOBehavior ioBehavior, CancellationTok

if (!m_hasMoreResults)
{
if (m_commandListPosition.CommandIndex < m_commandListPosition.Commands.Count)
if (m_commandListPosition.CommandIndex < m_commandListPosition.CommandCount)
{
Command = m_commandListPosition.Commands[m_commandListPosition.CommandIndex];
Command = m_commandListPosition.CommandAt(m_commandListPosition.CommandIndex);
using (Command.CancellableCommand.RegisterCancel(cancellationToken))
{
var writer = new ByteBufferWriter();
Expand Down Expand Up @@ -485,7 +485,7 @@ internal async Task InitAsync(CommandListPosition commandListPosition, ICommandP
await ReadOutParametersAsync(command, m_resultSet, ioBehavior, cancellationToken).ConfigureAwait(false);

// if the command list has multiple commands, keep reading until a result set is found
while (m_resultSet.State == ResultSetState.NoMoreData && commandListPosition.CommandIndex < commandListPosition.Commands.Count)
while (m_resultSet.State == ResultSetState.NoMoreData && commandListPosition.CommandIndex < commandListPosition.CommandCount)
{
await NextResultAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
}
Expand Down

0 comments on commit 7c54397

Please sign in to comment.