Skip to content

Optimize Dictionary<> allocation #393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Orm/Xtensive.Orm/Core/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,5 @@ public static string StripRoundBrackets(this string value)
? value
: value.Substring(start - 1, actualLength);
}

internal static bool Contains(this string str, string value, StringComparison comparison)
{
ArgumentNullException.ThrowIfNull(str);
ArgumentNullException.ThrowIfNull(value);

return str.IndexOf(value, comparison) >= 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
// Created by: Alexis Kochetov
// Created: 2009.04.27

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Orm.Rse.Providers;
Expand Down Expand Up @@ -115,26 +113,24 @@ internal override Expression VisitColumnExpression(ColumnExpression expression)

internal override Expression VisitConstructorExpression(ConstructorExpression expression)
{
var arguments = new List<Expression>();
var bindings = new Dictionary<MemberInfo, Expression>(expression.Bindings.Count);
var nativeBindings = new Dictionary<MemberInfo, Expression>(expression.NativeBindings.Count);
bool recreate = false;
var arguments = new Expression[expression.ConstructorArguments.Count];
int i = 0;
foreach (var argument in expression.ConstructorArguments) {
var result = Visit(argument);
if (result != argument)
recreate = true;
arguments.Add(result);
recreate |= (result != argument);
arguments[i++] = result;
}
foreach (var binding in expression.Bindings) {
var result = Visit(binding.Value);
if (result != binding.Value)
recreate = true;
recreate |= (result != binding.Value);
bindings.Add(binding.Key, result);
}
foreach (var nativeBinding in expression.NativeBindings) {
var result = Visit(nativeBinding.Value);
if (result!=nativeBinding.Value)
recreate = true;
recreate |= (result != nativeBinding.Value);
nativeBindings.Add(nativeBinding.Key, result);
}
if (!recreate)
Expand Down Expand Up @@ -163,4 +159,4 @@ public ExtendedExpressionReplacer(Func<Expression, Expression> replaceDelegate)
providerVisitor = new CompilableProviderVisitor(TranslateExpression);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,16 @@

namespace Xtensive.Orm.Linq.Materialization
{
internal sealed class MaterializationContext
{
#region Nested type: EntityMappingCache

private struct EntityMappingCache
{
public TypeMapping? SingleItem;
public Dictionary<int, TypeMapping> Items;
}

#endregion
using EntityMappingCache = (TypeMapping? SingleItem, Dictionary<int, TypeMapping> Items);

private readonly EntityMappingCache[] entityMappings;
internal class MaterializationContext(Session session, int entityCount)
{
private readonly EntityMappingCache[] entityMappings = new EntityMappingCache[entityCount];

/// <summary>
/// Gets the session in which materialization is executing.
/// </summary>
public Session Session { get; }
public Session Session => session;

/// <summary>
/// Gets model of current <see cref="DomainModel">domain model.</see>
Expand All @@ -52,12 +44,12 @@ private struct EntityMappingCache
public TypeMapping GetTypeMapping(int entityIndex, TypeInfo approximateType, int typeId, IEnumerable<(ColNum From, ColNum To)> columns)
{
ref var cache = ref entityMappings[entityIndex];
if (cache.SingleItem is TypeMapping result) {
if (cache.SingleItem is { } result) {
return typeId == ResolveTypeToNodeSpecificTypeIdentifier(result.Type)
? result
: throw new ArgumentOutOfRangeException("typeId");
}
if (cache.Items.TryGetValue(typeId, out result))
if (cache.Items?.TryGetValue(typeId, out result) == true)
return result;

var type = TypeIdRegistry[typeId];
Expand All @@ -83,22 +75,11 @@ public TypeMapping GetTypeMapping(int entityIndex, TypeInfo approximateType, int
if (type.Hierarchy.Root.IsLeaf && approximateType==type)
cache.SingleItem = result;
else
cache.Items.Add(typeId, result);
(cache.Items ??= new()).Add(typeId, result);

return result;
}

private int ResolveTypeToNodeSpecificTypeIdentifier(TypeInfo typeInfo) => TypeIdRegistry[typeInfo];

// Constructors

public MaterializationContext(Session session, int entityCount)
{
Session = session;

entityMappings = new EntityMappingCache[entityCount];
for (int i = 0; i < entityMappings.Length; i++)
entityMappings[i] = new() { Items = new Dictionary<int, TypeMapping>() };
}
}
}
25 changes: 6 additions & 19 deletions Orm/Xtensive.Orm/Orm/Linq/Materialization/Materializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,13 @@
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.

using System;
using Xtensive.Core;
using Xtensive.Orm.Rse;

namespace Xtensive.Orm.Linq.Materialization
{
internal readonly struct Materializer
{
private readonly Func<RecordSetReader, Session, ParameterContext, object> materializeMethod;

public QueryResult<T> Invoke<T>(RecordSetReader recordSetReader, Session session, ParameterContext parameterContext)
{
var reader = (IMaterializingReader<T>)
materializeMethod.Invoke(recordSetReader, session, parameterContext);
return new QueryResult<T>(reader, session.GetLifetimeToken());
}
namespace Xtensive.Orm.Linq.Materialization;

public Materializer(Func<RecordSetReader,Session,ParameterContext,object> materializeMethod)
{
this.materializeMethod = materializeMethod;
}
}
}
internal readonly struct Materializer(Func<RecordSetReader, Session, ParameterContext, object> materializeMethod)
{
public QueryResult<T> Invoke<T>(RecordSetReader recordSetReader, Session session, ParameterContext parameterContext) =>
new((IMaterializingReader<T>) materializeMethod(recordSetReader, session, parameterContext), session.GetLifetimeToken());
}
17 changes: 8 additions & 9 deletions Orm/Xtensive.Orm/Orm/Linq/Translator.Materialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
// Created by: Alexis Kochetov
// Created: 2009.05.28

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Xtensive.Core;
Expand Down Expand Up @@ -34,6 +31,9 @@ internal sealed partial class Translator
private static readonly ParameterExpression Session = Expression.Parameter(typeof(Session), "session");
private static readonly IReadOnlyList<ParameterExpression> TupleReaderSessionParameterContext = [TupleReader, Session, ParameterContext];

private static readonly Expression<Func<Session, int, MaterializationContext>> MaterializationContextCtor =
(s, entityCount) => new MaterializationContext(s, entityCount);

private readonly CompiledQueryProcessingScope compiledQueryScope;

public TranslatedQuery Translate()
Expand Down Expand Up @@ -80,16 +80,17 @@ internal TranslatedQuery Translate(ProjectionExpression projection,
return translatedQuery;
}

private static readonly IReadOnlyList<ColNum> SingleColumn = [0];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's not worth to keep it in a variable. Compiler should be smart enough to do it on its own.

Copy link
Collaborator Author

@SergeiPavlov SergeiPavlov Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why to rely on compiler if it is easy to make it optimal manually?
And we are not sure about the compiler.


private static ProjectionExpression Optimize(ProjectionExpression origin)
{
var originProvider = origin.ItemProjector.DataSource;

var usedColumns = origin.ItemProjector
.GetColumns(ColumnExtractionModes.KeepSegment | ColumnExtractionModes.KeepTypeId | ColumnExtractionModes.OmitLazyLoad)
.ToList();
.GetColumns(ColumnExtractionModes.KeepSegment | ColumnExtractionModes.KeepTypeId | ColumnExtractionModes.OmitLazyLoad);

if (usedColumns.Count == 0)
usedColumns.Add(0);
usedColumns = SingleColumn;
if (usedColumns.Count < origin.ItemProjector.DataSource.Header.Length) {
var resultProvider = new SelectProvider(originProvider, usedColumns);
using var columnMap = new ColumnMap(usedColumns);
Expand Down Expand Up @@ -134,9 +135,7 @@ private Materializer BuildMaterializer(ProjectionExpression projection, IReadOnl
: MaterializationHelper.CreateItemMaterializerMethodInfo.CachedMakeGenericMethodInvoker(elementType);
var itemMaterializer = itemMaterializerFactoryMethod.Invoke(null, materializationInfo.Expression, itemProjector.AggregateType);

Expression<Func<Session, int, MaterializationContext>> materializationContextCtor =
(s, entityCount) => new MaterializationContext(s, entityCount);
var materializationContextExpression = materializationContextCtor
var materializationContextExpression = MaterializationContextCtor
.BindParameters(Session, Expr.Constant(materializationInfo.EntitiesInRow));

Expression body = Expression.Call(
Expand Down