Skip to content

Commit

Permalink
Reduce accesses to virtual properties in query (#24775)
Browse files Browse the repository at this point in the history
For perf
  • Loading branch information
vonzshik authored Apr 28, 2021
1 parent c375551 commit 6f7f730
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SingleQueryResultCoordinator
public sealed class SingleQueryResultCoordinator
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -20,49 +20,47 @@ public class SingleQueryResultCoordinator
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SingleQueryResultCoordinator()
{
ResultContext = new ResultContext();
}
=> ResultContext = new ResultContext();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ResultContext ResultContext { get; }
public ResultContext ResultContext { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool ResultReady { get; set; }
public bool ResultReady { get; set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool? HasNext { get; set; }
public bool? HasNext { get; set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IList<SingleQueryCollectionContext?> Collections { get; } = new List<SingleQueryCollectionContext?>();
public IList<SingleQueryCollectionContext?> Collections { get; } = new List<SingleQueryCollectionContext?>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void SetSingleQueryCollectionContext(
public void SetSingleQueryCollectionContext(
int collectionId,
SingleQueryCollectionContext singleQueryCollectionContext)
{
Expand Down
46 changes: 28 additions & 18 deletions src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ private sealed class Enumerator : IEnumerator<T>

private IRelationalCommand? _relationalCommand;
private RelationalDataReader? _dataReader;
private DbDataReader? _dbDataReader;
private SingleQueryResultCoordinator? _resultCoordinator;

public Enumerator(SingleQueryingEnumerable<T> queryingEnumerable)
Expand Down Expand Up @@ -164,11 +165,10 @@ public bool MoveNext()
if (_dataReader == null)
{
_relationalQueryContext.ExecutionStrategyFactory.Create()
.Execute(this, (_, enumerator) => InitializeReader(enumerator), null);
.Execute(this, static (_, enumerator) => InitializeReader(enumerator), null);
}

var hasNext = _resultCoordinator!.HasNext ?? _dataReader!.Read();
Current = default!;

if (hasNext)
{
Expand All @@ -177,28 +177,30 @@ public bool MoveNext()
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
_relationalQueryContext, _dbDataReader!, _resultCoordinator.ResultContext, _resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!_dataReader.Read())
if (!_dataReader!.Read())
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
_relationalQueryContext, _dbDataReader!, _resultCoordinator.ResultContext, _resultCoordinator);

break;
}
}
}
else
{
Current = default!;
}

return hasNext;
}
Expand All @@ -225,14 +227,15 @@ private static bool InitializeReader(Enumerator enumerator)
var relationalCommand = enumerator._relationalCommand = enumerator._relationalQueryContext.Connection.RentCommand();
relationalCommand.PopulateFrom(relationalCommandTemplate);

enumerator._dataReader = relationalCommand.ExecuteReader(
var dataReader = enumerator._dataReader = relationalCommand.ExecuteReader(
new RelationalCommandParameterObject(
enumerator._relationalQueryContext.Connection,
enumerator._relationalQueryContext.ParameterValues,
enumerator._relationalCommandCache.ReaderColumns,
enumerator._relationalQueryContext.Context,
enumerator._relationalQueryContext.CommandLogger,
enumerator._detailedErrorsEnabled));
enumerator._dbDataReader = dataReader.DbDataReader;

enumerator._resultCoordinator = new SingleQueryResultCoordinator();

Expand All @@ -248,6 +251,7 @@ public void Dispose()
_relationalQueryContext.Connection.ReturnCommand(_relationalCommand!);
_dataReader.Dispose();
_dataReader = null;
_dbDataReader = null;
}
}

Expand All @@ -265,9 +269,11 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<T>
private readonly bool _standAloneStateManager;
private readonly bool _detailedErrorsEnabled;
private readonly IConcurrencyDetector? _concurrencyDetector;
private readonly CancellationToken _cancellationToken;

private IRelationalCommand? _relationalCommand;
private RelationalDataReader? _dataReader;
private DbDataReader? _dbDataReader;
private SingleQueryResultCoordinator? _resultCoordinator;

public AsyncEnumerator(SingleQueryingEnumerable<T> queryingEnumerable)
Expand All @@ -279,6 +285,7 @@ public AsyncEnumerator(SingleQueryingEnumerable<T> queryingEnumerable)
_queryLogger = queryingEnumerable._queryLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled;
_cancellationToken = _relationalQueryContext.CancellationToken;
Current = default!;

_concurrencyDetector = queryingEnumerable._threadSafetyChecksEnabled
Expand All @@ -301,15 +308,14 @@ public async ValueTask<bool> MoveNextAsync()
await _relationalQueryContext.ExecutionStrategyFactory.Create()
.ExecuteAsync(
this,
(_, enumerator, cancellationToken) => InitializeReaderAsync(enumerator, cancellationToken),
static (_, enumerator, cancellationToken) => InitializeReaderAsync(enumerator, cancellationToken),
null,
_relationalQueryContext.CancellationToken)
_cancellationToken)
.ConfigureAwait(false);
}

var hasNext = _resultCoordinator!.HasNext
?? await _dataReader!.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false);
Current = default!;
?? await _dataReader!.ReadAsync(_cancellationToken).ConfigureAwait(false);

if (hasNext)
{
Expand All @@ -318,28 +324,30 @@ await _relationalQueryContext.ExecutionStrategyFactory.Create()
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
_relationalQueryContext, _dbDataReader!, _resultCoordinator.ResultContext, _resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false))
if (!await _dataReader!.ReadAsync(_cancellationToken).ConfigureAwait(false))
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
_relationalQueryContext, _dbDataReader!, _resultCoordinator.ResultContext, _resultCoordinator);

break;
}
}
}
else
{
Current = default!;
}

return hasNext;
}
Expand All @@ -366,7 +374,7 @@ private static async Task<bool> InitializeReaderAsync(AsyncEnumerator enumerator
var relationalCommand = enumerator._relationalCommand = enumerator._relationalQueryContext.Connection.RentCommand();
relationalCommand.PopulateFrom(relationalCommandTemplate);

enumerator._dataReader = await relationalCommand.ExecuteReaderAsync(
var dataReader = enumerator._dataReader = await relationalCommand.ExecuteReaderAsync(
new RelationalCommandParameterObject(
enumerator._relationalQueryContext.Connection,
enumerator._relationalQueryContext.ParameterValues,
Expand All @@ -376,6 +384,7 @@ private static async Task<bool> InitializeReaderAsync(AsyncEnumerator enumerator
enumerator._detailedErrorsEnabled),
cancellationToken)
.ConfigureAwait(false);
enumerator._dbDataReader = dataReader.DbDataReader;

enumerator._resultCoordinator = new SingleQueryResultCoordinator();

Expand All @@ -392,6 +401,7 @@ public ValueTask DisposeAsync()

var dataReader = _dataReader;
_dataReader = null;
_dbDataReader = null;

return dataReader.DisposeAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SplitQueryResultCoordinator
public sealed class SplitQueryResultCoordinator
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -21,43 +21,39 @@ public class SplitQueryResultCoordinator
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SplitQueryResultCoordinator()
{
ResultContext = new ResultContext();
}
=> ResultContext = new ResultContext();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ResultContext ResultContext { get; }
public ResultContext ResultContext { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IList<SplitQueryCollectionContext?> Collections { get; } = new List<SplitQueryCollectionContext?>();
public IList<SplitQueryCollectionContext?> Collections { get; } = new List<SplitQueryCollectionContext?>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IList<SplitQueryDataReaderContext?> DataReaders { get; } = new List<SplitQueryDataReaderContext?>();
public IList<SplitQueryDataReaderContext?> DataReaders { get; } = new List<SplitQueryDataReaderContext?>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void SetDataReader(
int collectionId,
RelationalDataReader relationalDataReader)
public void SetDataReader(int collectionId, RelationalDataReader relationalDataReader)
{
while (DataReaders.Count <= collectionId)
{
Expand All @@ -73,9 +69,7 @@ public virtual void SetDataReader(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void SetSplitQueryCollectionContext(
int collectionId,
SplitQueryCollectionContext splitQueryCollectionContext)
public void SetSplitQueryCollectionContext(int collectionId, SplitQueryCollectionContext splitQueryCollectionContext)
{
while (Collections.Count <= collectionId)
{
Expand Down
Loading

0 comments on commit 6f7f730

Please sign in to comment.