Skip to content

Commit 12bc6cf

Browse files
authored
Merge pull request #194 from servicetitan/upstream/StaticTyped_InternalQueryCache
Split type-unsafe InternalQueryCache into 4 statically typed caches
2 parents fc5f711 + 1d70379 commit 12bc6cf

9 files changed

Lines changed: 146 additions & 148 deletions

File tree

Orm/Xtensive.Orm/Orm/Entity.cs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2007-2020 Xtensive LLC.
1+
// Copyright (C) 2007-2021 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Dmitri Maximov
@@ -66,6 +66,12 @@ public abstract class Entity : Persistent,
6666
ISerializable,
6767
IDeserializationCallback
6868
{
69+
private static readonly Func<(TypeInfo typeInfo, LockMode lockMode, LockBehavior lockBehavior), Session, ExecutableProvider> ExecutableProviderGenerator = ((TypeInfo typeInfo, LockMode lockMode, LockBehavior lockBehavior) t, Session session) =>
70+
session.Compile(t.typeInfo.Indexes.PrimaryIndex.GetQuery()
71+
.Seek(context => context.GetValue(keyParameter))
72+
.Lock(() => t.lockMode, () => t.lockBehavior)
73+
.Select(Array.Empty<int>()));
74+
6975
private static readonly Parameter<Tuple> keyParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
7076
private readonly bool changeVersionOnSetAttempt;
7177
private EntityState state;
@@ -265,17 +271,7 @@ public void Lock(LockMode lockMode, LockBehavior lockBehavior)
265271
{
266272
var parameterContext = new ParameterContext();
267273
parameterContext.SetValue(keyParameter, Key.Value);
268-
object key = (TypeInfo, lockMode, lockBehavior);
269-
Func<object, object> generator = tripletObj => {
270-
var triplet = ((TypeInfo, LockMode, LockBehavior)) tripletObj;
271-
var index = triplet.Item1.Indexes.PrimaryIndex;
272-
var query = index.GetQuery()
273-
.Seek(context => context.GetValue(keyParameter))
274-
.Lock(() => triplet.Item2, () => triplet.Item3)
275-
.Select(Array.Empty<int>());
276-
return Session.Compile(query);
277-
};
278-
var source = (ExecutableProvider) Session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
274+
var source = Session.StorageNode.EntityLockProviderCache.GetOrAdd((TypeInfo, lockMode, lockBehavior), ExecutableProviderGenerator, Session);
279275
using var recordSetReader = source.GetRecordSetReader(Session, parameterContext);
280276
recordSetReader.MoveNext();
281277
}

Orm/Xtensive.Orm/Orm/EntitySetBase.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ public abstract class EntitySetBase : SessionBound,
3939
#if DEBUG
4040
private static readonly string storageTestsAssemblyPrefix = "Xtensive.Orm.Tests";
4141
#endif
42-
private static readonly object entitySetCachingRegion = new object();
4342
private static readonly Parameter<Tuple> keyParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
4443
internal static readonly Parameter<Entity> ownerParameter = new Parameter<Entity>("Owner");
4544

45+
private static readonly Func<FieldInfo, EntitySetBase, EntitySetTypeState> EntitySetTypeStateFactory = BuildEntitySetTypeState;
46+
4647
private readonly Entity owner;
4748
private readonly CombineTransform auxilaryTypeKeyTransform;
4849
private readonly bool skipOwnerVersionChange;
@@ -923,14 +924,11 @@ private static Key GetOwnerKey(Persistent owner)
923924
private EntitySetTypeState GetEntitySetTypeState()
924925
{
925926
EnsureOwnerIsNotRemoved();
926-
object key = new Pair<object, FieldInfo>(entitySetCachingRegion, Field);
927-
Func<object, object> generator = k => BuildEntitySetTypeState(k, this);
928-
return (EntitySetTypeState) Session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
927+
return Session.StorageNode.EntitySetTypeStateCache.GetOrAdd(Field, EntitySetTypeStateFactory, this);
929928
}
930929

931-
private static EntitySetTypeState BuildEntitySetTypeState(object key, EntitySetBase entitySet)
930+
private static EntitySetTypeState BuildEntitySetTypeState(FieldInfo field, EntitySetBase entitySet)
932931
{
933-
var field = ((Pair<object, FieldInfo>) key).Second;
934932
var association = field.Associations.Last();
935933
var query = association.UnderlyingIndex.GetQuery().Seek(context => context.GetValue(keyParameter));
936934
var seek = entitySet.Session.Compile(query);

Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntityContainer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ namespace Xtensive.Orm.Internals.Prefetch
1616
[Serializable]
1717
internal abstract class EntityContainer
1818
{
19-
private static readonly object indexSeekCachingRegion = new object();
2019
private static readonly Parameter<Tuple> seekParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
2120

2221
private SortedDictionary<int, ColumnInfo> columns;

Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntityGroupTask.cs

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,64 +15,74 @@
1515

1616
namespace Xtensive.Orm.Internals.Prefetch
1717
{
18-
[Serializable]
19-
internal sealed class EntityGroupTask : IEquatable<EntityGroupTask>
18+
internal readonly struct RecordSetCacheKey : IEquatable<RecordSetCacheKey>
2019
{
21-
#region Nested classes
20+
public readonly int[] ColumnIndexes;
21+
public readonly TypeInfo Type;
22+
private readonly int cachedHashCode;
2223

23-
private struct CacheKey : IEquatable<CacheKey>
24+
public bool Equals(RecordSetCacheKey other)
2425
{
25-
public readonly int[] ColumnIndexes;
26-
public readonly TypeInfo Type;
27-
private readonly int cachedHashCode;
26+
if (!Type.Equals(other.Type)) {
27+
return false;
28+
}
2829

29-
public bool Equals(CacheKey other)
30-
{
31-
if (!Type.Equals(other.Type)) {
32-
return false;
33-
}
30+
if (ColumnIndexes.Length != other.ColumnIndexes.Length) {
31+
return false;
32+
}
3433

35-
if (ColumnIndexes.Length != other.ColumnIndexes.Length) {
34+
for (var i = ColumnIndexes.Length - 1; i >= 0; i--) {
35+
if (ColumnIndexes[i] != other.ColumnIndexes[i]) {
3636
return false;
3737
}
38-
39-
for (var i = ColumnIndexes.Length - 1; i >= 0; i--) {
40-
if (ColumnIndexes[i] != other.ColumnIndexes[i]) {
41-
return false;
42-
}
43-
}
44-
45-
return true;
4638
}
4739

48-
public override bool Equals(object obj) =>
49-
obj is CacheKey other && Equals(other);
40+
return true;
41+
}
5042

51-
public override int GetHashCode() => cachedHashCode;
43+
public override bool Equals(object obj) =>
44+
obj is RecordSetCacheKey other && Equals(other);
5245

46+
public override int GetHashCode() => cachedHashCode;
5347

54-
// Constructors
5548

56-
public CacheKey(int[] columnIndexes, TypeInfo type, int cachedHashCode)
57-
{
58-
ColumnIndexes = columnIndexes;
59-
Type = type;
60-
this.cachedHashCode = cachedHashCode;
61-
}
62-
}
49+
// Constructors
6350

64-
#endregion
51+
public RecordSetCacheKey(int[] columnIndexes, TypeInfo type, int cachedHashCode)
52+
{
53+
ColumnIndexes = columnIndexes;
54+
Type = type;
55+
this.cachedHashCode = cachedHashCode;
56+
}
57+
}
6558

59+
[Serializable]
60+
internal sealed class EntityGroupTask : IEquatable<EntityGroupTask>
61+
{
6662
private const int MaxKeyCountInOneStatement = 40;
67-
private static readonly object recordSetCachingRegion = new object();
6863
private static readonly Parameter<IEnumerable<Tuple>> includeParameter =
6964
new Parameter<IEnumerable<Tuple>>("Keys");
7065

66+
private static readonly Func<RecordSetCacheKey, CompilableProvider> CreateRecordSet = cachingKey => {
67+
var selectedColumnIndexes = cachingKey.ColumnIndexes;
68+
var primaryIndex = cachingKey.Type.Indexes.PrimaryIndex;
69+
var keyColumnsCount = primaryIndex.KeyColumns.Count;
70+
var keyColumnIndexes = new int[keyColumnsCount];
71+
foreach (var index in Enumerable.Range(0, keyColumnsCount)) {
72+
keyColumnIndexes[index] = index;
73+
}
74+
75+
var columnCollectionLength = primaryIndex.Columns.Count;
76+
return primaryIndex.GetQuery().Include(IncludeAlgorithm.ComplexCondition,
77+
true, context => context.GetValue(includeParameter), $"includeColumnName-{Guid.NewGuid()}",
78+
keyColumnIndexes).Filter(t => t.GetValue<bool>(columnCollectionLength)).Select(selectedColumnIndexes);
79+
};
80+
7181
private Dictionary<Key, bool> keys;
7282
private readonly TypeInfo type;
7383
private readonly PrefetchManager manager;
7484
private List<QueryTask> queryTasks;
75-
private readonly CacheKey cacheKey;
85+
private readonly RecordSetCacheKey cacheKey;
7686

7787
public CompilableProvider Provider { get; private set; }
7888

@@ -132,30 +142,12 @@ private QueryTask CreateQueryTask(List<Tuple> currentKeySet)
132142
{
133143
var parameterContext = new ParameterContext();
134144
parameterContext.SetValue(includeParameter, currentKeySet);
135-
object key = new Pair<object, CacheKey>(recordSetCachingRegion, cacheKey);
136-
Func<object, object> generator = CreateRecordSet;
137145
var session = manager.Owner.Session;
138-
Provider = (CompilableProvider) session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
146+
Provider = session.StorageNode.EntityFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSet);
139147
var executableProvider = session.Compile(Provider);
140148
return new QueryTask(executableProvider, session.GetLifetimeToken(), parameterContext);
141149
}
142150

143-
private static CompilableProvider CreateRecordSet(object cachingKey)
144-
{
145-
var pair = (Pair<object, CacheKey>) cachingKey;
146-
var selectedColumnIndexes = pair.Second.ColumnIndexes;
147-
var keyColumnsCount = pair.Second.Type.Indexes.PrimaryIndex.KeyColumns.Count;
148-
var keyColumnIndexes = new int[keyColumnsCount];
149-
foreach (var index in Enumerable.Range(0, keyColumnsCount)) {
150-
keyColumnIndexes[index] = index;
151-
}
152-
153-
var columnCollectionLength = pair.Second.Type.Indexes.PrimaryIndex.Columns.Count;
154-
return pair.Second.Type.Indexes.PrimaryIndex.GetQuery().Include(IncludeAlgorithm.ComplexCondition,
155-
true, context => context.GetValue(includeParameter), $"includeColumnName-{Guid.NewGuid()}",
156-
keyColumnIndexes).Filter(t => t.GetValue<bool>(columnCollectionLength)).Select(selectedColumnIndexes);
157-
}
158-
159151
private void PutLoadedStatesInCache(IEnumerable<Tuple> queryResult, EntityDataReader reader,
160152
HashSet<Key> foundedKeys)
161153
{
@@ -219,7 +211,7 @@ public EntityGroupTask(TypeInfo type, int[] columnIndexes, PrefetchManager manag
219211
}
220212

221213
cachedHashCode ^= type.GetHashCode();
222-
cacheKey = new CacheKey(columnIndexes, type, cachedHashCode);
214+
cacheKey = new RecordSetCacheKey(columnIndexes, type, cachedHashCode);
223215
}
224216
}
225217
}

Orm/Xtensive.Orm/Orm/Internals/Prefetch/EntitySetTask.cs

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2009-2020 Xtensive LLC.
1+
// Copyright (C) 2009-2021 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44
// Created by: Alexander Nikolaev
@@ -19,53 +19,61 @@
1919

2020
namespace Xtensive.Orm.Internals.Prefetch
2121
{
22-
[Serializable]
23-
internal sealed class EntitySetTask : IEquatable<EntitySetTask>
22+
internal readonly struct ItemsQueryCacheKey : IEquatable<ItemsQueryCacheKey>
2423
{
25-
#region Nested classes
24+
public readonly FieldInfo ReferencingField;
25+
public readonly int? ItemCountLimit;
26+
private readonly int cachedHashCode;
2627

27-
private struct CacheKey : IEquatable<CacheKey>
28+
public bool Equals(ItemsQueryCacheKey other)
2829
{
29-
public readonly FieldInfo ReferencingField;
30-
public readonly int? ItemCountLimit;
31-
private readonly int cachedHashCode;
32-
33-
public bool Equals(CacheKey other)
34-
{
35-
return (ItemCountLimit == null) == (other.ItemCountLimit == null)
36-
&& Equals(other.ReferencingField, ReferencingField);
37-
}
30+
return (ItemCountLimit == null) == (other.ItemCountLimit == null)
31+
&& Equals(other.ReferencingField, ReferencingField);
32+
}
3833

39-
public override bool Equals(object obj) =>
40-
obj is CacheKey other && Equals(other);
34+
public override bool Equals(object obj) =>
35+
obj is ItemsQueryCacheKey other && Equals(other);
4136

42-
public override int GetHashCode() => cachedHashCode;
37+
public override int GetHashCode() => cachedHashCode;
4338

44-
// Constructors
39+
// Constructors
4540

46-
public CacheKey(FieldInfo referencingField, int? itemCountLimit)
47-
{
48-
ReferencingField = referencingField;
49-
ItemCountLimit = itemCountLimit;
50-
unchecked {
51-
cachedHashCode = (ReferencingField.GetHashCode()*397)
52-
^ (ItemCountLimit.HasValue ? 1 : 0);
53-
}
41+
public ItemsQueryCacheKey(FieldInfo referencingField, int? itemCountLimit)
42+
{
43+
ReferencingField = referencingField;
44+
ItemCountLimit = itemCountLimit;
45+
unchecked {
46+
cachedHashCode = (ReferencingField.GetHashCode() * 397)
47+
^ (ItemCountLimit.HasValue ? 1 : 0);
5448
}
5549
}
50+
}
5651

57-
#endregion
58-
59-
private static readonly object itemsQueryCachingRegion = new object();
52+
[Serializable]
53+
internal sealed class EntitySetTask : IEquatable<EntitySetTask>
54+
{
6055
private static readonly Parameter<Tuple> ownerParameter = new Parameter<Tuple>(WellKnown.KeyFieldName);
6156
private static readonly Parameter<int> itemCountLimitParameter = new Parameter<int>("ItemCountLimit");
6257

58+
private static readonly Func<ItemsQueryCacheKey, CompilableProvider> CreateRecordSetLoadingItems = cachingKey => {
59+
var association = cachingKey.ReferencingField.Associations.Last();
60+
var primaryTargetIndex = association.TargetType.Indexes.PrimaryIndex;
61+
var resultColumns = new List<int>(primaryTargetIndex.Columns.Count);
62+
var result = association.AuxiliaryType == null
63+
? CreateQueryForDirectAssociation(cachingKey, primaryTargetIndex, resultColumns)
64+
: CreateQueryForAssociationViaAuxType(cachingKey, primaryTargetIndex, resultColumns);
65+
result = result.Select(resultColumns);
66+
if (cachingKey.ItemCountLimit != null)
67+
result = result.Take(context => context.GetValue(itemCountLimitParameter));
68+
return result;
69+
};
70+
6371
private readonly Key ownerKey;
6472
private readonly bool isOwnerCached;
6573
private readonly PrefetchManager manager;
6674
private QueryTask itemsQueryTask;
6775
private readonly PrefetchFieldDescriptor referencingFieldDescriptor;
68-
private readonly CacheKey cacheKey;
76+
private readonly ItemsQueryCacheKey cacheKey;
6977

7078
public FieldInfo ReferencingField {get { return referencingFieldDescriptor.Field; } }
7179

@@ -166,36 +174,19 @@ private QueryTask CreateQueryTask()
166174
parameterContext.SetValue(itemCountLimitParameter, ItemCountLimit.Value);
167175
}
168176

169-
object key = new Pair<object, CacheKey>(itemsQueryCachingRegion, cacheKey);
170-
Func<object, object> generator = CreateRecordSetLoadingItems;
171177
var session = manager.Owner.Session;
172178
var scope = new CompiledQueryProcessingScope(null, null, parameterContext, false);
173-
QueryProvider = (CompilableProvider) session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
179+
QueryProvider = session.StorageNode.EntitySetFetchQueryCache.GetOrAdd(cacheKey, CreateRecordSetLoadingItems);
174180
ExecutableProvider executableProvider;
175181
using (scope.Enter()) {
176182
executableProvider = session.Compile(QueryProvider);
177183
}
178184
return new QueryTask(executableProvider, session.GetLifetimeToken(), parameterContext);
179185
}
180186

181-
private static CompilableProvider CreateRecordSetLoadingItems(object cachingKey)
182-
{
183-
var pair = (Pair<object, CacheKey>) cachingKey;
184-
var association = pair.Second.ReferencingField.Associations.Last();
185-
var primaryTargetIndex = association.TargetType.Indexes.PrimaryIndex;
186-
var resultColumns = new List<int>(primaryTargetIndex.Columns.Count);
187-
var result = association.AuxiliaryType == null
188-
? CreateQueryForDirectAssociation(pair, primaryTargetIndex, resultColumns)
189-
: CreateQueryForAssociationViaAuxType(pair, primaryTargetIndex, resultColumns);
190-
result = result.Select(resultColumns);
191-
if (pair.Second.ItemCountLimit != null)
192-
result = result.Take(context => context.GetValue(itemCountLimitParameter));
193-
return result;
194-
}
195-
196-
private static CompilableProvider CreateQueryForAssociationViaAuxType(Pair<object, CacheKey> pair, IndexInfo primaryTargetIndex, List<int> resultColumns)
187+
private static CompilableProvider CreateQueryForAssociationViaAuxType(in ItemsQueryCacheKey cachingKey, IndexInfo primaryTargetIndex, List<int> resultColumns)
197188
{
198-
var association = pair.Second.ReferencingField.Associations.Last();
189+
var association = cachingKey.ReferencingField.Associations.Last();
199190
var associationIndex = association.UnderlyingIndex;
200191
var joiningColumns = GetJoiningColumnIndexes(primaryTargetIndex, associationIndex,
201192
association.AuxiliaryType != null);
@@ -214,10 +205,10 @@ private static CompilableProvider CreateQueryForAssociationViaAuxType(Pair<objec
214205
.Join(primaryTargetIndex.GetQuery(), joiningColumns);
215206
}
216207

217-
private static CompilableProvider CreateQueryForDirectAssociation(Pair<object, CacheKey> pair, IndexInfo primaryTargetIndex, List<int> resultColumns)
208+
private static CompilableProvider CreateQueryForDirectAssociation(in ItemsQueryCacheKey cachingKey, IndexInfo primaryTargetIndex, List<int> resultColumns)
218209
{
219210
AddResultColumnIndexes(resultColumns, primaryTargetIndex, 0);
220-
var association = pair.Second.ReferencingField.Associations.Last();
211+
var association = cachingKey.ReferencingField.Associations.Last();
221212
var field = association.Reversed.OwnerField;
222213
var keyColumnTypes = field.Columns.Select(column => column.ValueType).ToList();
223214
return primaryTargetIndex
@@ -273,7 +264,7 @@ public EntitySetTask(Key ownerKey, PrefetchFieldDescriptor referencingFieldDescr
273264
this.isOwnerCached = isOwnerCached;
274265
ItemCountLimit = referencingFieldDescriptor.EntitySetItemCountLimit;
275266
this.manager = manager;
276-
cacheKey = new CacheKey(ReferencingField, ItemCountLimit);
267+
cacheKey = new ItemsQueryCacheKey(ReferencingField, ItemCountLimit);
277268
}
278269
}
279270
}

0 commit comments

Comments
 (0)