Skip to content

Commit 10c2735

Browse files
committed
Split type-unsafe InternalQueryCache into 4 statically typed caches (#57)
* Split type-unsafe InternalQueryCache into three statically typed caches * Remove unused indexSeekCachingRegion * Make BuildEntitySetTypeState static delegate * Rename EntitySetTask.CacheKey -> ItemsQueryCacheKey * Make CacheKey structs readonly
1 parent 34b30ed commit 10c2735

9 files changed

Lines changed: 168 additions & 184 deletions

File tree

Orm/Xtensive.Orm/Orm/Entity.cs

Lines changed: 12 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
@@ -260,22 +260,22 @@ public void RemoveLater()
260260
RemoveLaterInternal(EntityRemoveReason.User);
261261
}
262262

263+
private ExecutableProvider ExecutableProviderGenerator((TypeInfo typeInfo, LockMode lockMode, LockBehavior lockBehavior) t)
264+
{
265+
var index = t.typeInfo.Indexes.PrimaryIndex;
266+
var query = index.GetQuery()
267+
.Seek(context => context.GetValue(keyParameter))
268+
.Lock(() => t.lockMode, () => t.lockBehavior)
269+
.Select(Array.Empty<int>());
270+
return Session.Compile(query);
271+
}
272+
263273
/// <inheritdoc/>
264274
public void Lock(LockMode lockMode, LockBehavior lockBehavior)
265275
{
266276
var parameterContext = new ParameterContext();
267277
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);
278+
var source = Session.StorageNode.InternalExecutableProviderCache.GetOrAdd((TypeInfo, lockMode, lockBehavior), ExecutableProviderGenerator);
279279
using var recordSetReader = source.GetRecordSetReader(Session, parameterContext);
280280
recordSetReader.MoveNext();
281281
}

Orm/Xtensive.Orm/Orm/EntitySetBase.cs

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,46 @@ 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<object, EntitySetBase, EntitySetTypeState> BuildEntitySetTypeState = (object key, EntitySetBase entitySet) => {
46+
var field = ((Pair<object, FieldInfo>) key).Second;
47+
var association = field.Associations.Last();
48+
var query = association.UnderlyingIndex.GetQuery().Seek(context => context.GetValue(keyParameter));
49+
var seek = entitySet.Session.Compile(query);
50+
var ownerDescriptor = association.OwnerType.Key.TupleDescriptor;
51+
var targetDescriptor = association.TargetType.Key.TupleDescriptor;
52+
53+
var itemColumnOffsets = association.AuxiliaryType == null
54+
? association.UnderlyingIndex.ValueColumns
55+
.Where(ci => ci.IsPrimaryKey)
56+
.Select(ci => ci.Field.MappingInfo.Offset)
57+
.ToList()
58+
: Enumerable.Range(0, targetDescriptor.Count).ToList();
59+
60+
var keyFieldCount = ownerDescriptor.Count + itemColumnOffsets.Count;
61+
var keyFieldTypes = ownerDescriptor
62+
.Concat(itemColumnOffsets.Select(i => targetDescriptor[i]))
63+
.ToArray(keyFieldCount);
64+
var keyDescriptor = TupleDescriptor.Create(keyFieldTypes);
65+
66+
var map = Enumerable.Range(0, ownerDescriptor.Count)
67+
.Select(i => new Pair<int, int>(0, i))
68+
.Concat(itemColumnOffsets.Select(i => new Pair<int, int>(1, i)))
69+
.ToArray(keyFieldCount);
70+
var seekTransform = new MapTransform(true, keyDescriptor, map);
71+
72+
Func<Tuple, Entity> itemCtor = null;
73+
if (association.AuxiliaryType != null) {
74+
itemCtor = DelegateHelper.CreateDelegate<Func<Tuple, Entity>>(null,
75+
association.AuxiliaryType.UnderlyingType, DelegateHelper.AspectedFactoryMethodName,
76+
Array.Empty<Type>());
77+
}
78+
79+
return new EntitySetTypeState(seek, seekTransform, itemCtor, entitySet.GetItemCountQueryDelegate(field));
80+
};
81+
4682
private readonly Entity owner;
4783
private readonly CombineTransform auxilaryTypeKeyTransform;
4884
private readonly bool skipOwnerVersionChange;
@@ -923,47 +959,7 @@ private static Key GetOwnerKey(Persistent owner)
923959
private EntitySetTypeState GetEntitySetTypeState()
924960
{
925961
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);
929-
}
930-
931-
private static EntitySetTypeState BuildEntitySetTypeState(object key, EntitySetBase entitySet)
932-
{
933-
var field = ((Pair<object, FieldInfo>) key).Second;
934-
var association = field.Associations.Last();
935-
var query = association.UnderlyingIndex.GetQuery().Seek(context => context.GetValue(keyParameter));
936-
var seek = entitySet.Session.Compile(query);
937-
var ownerDescriptor = association.OwnerType.Key.TupleDescriptor;
938-
var targetDescriptor = association.TargetType.Key.TupleDescriptor;
939-
940-
var itemColumnOffsets = association.AuxiliaryType == null
941-
? association.UnderlyingIndex.ValueColumns
942-
.Where(ci => ci.IsPrimaryKey)
943-
.Select(ci => ci.Field.MappingInfo.Offset)
944-
.ToList()
945-
: Enumerable.Range(0, targetDescriptor.Count).ToList();
946-
947-
var keyFieldCount = ownerDescriptor.Count + itemColumnOffsets.Count;
948-
var keyFieldTypes = ownerDescriptor
949-
.Concat(itemColumnOffsets.Select(i => targetDescriptor[i]))
950-
.ToArray(keyFieldCount);
951-
var keyDescriptor = TupleDescriptor.Create(keyFieldTypes);
952-
953-
var map = Enumerable.Range(0, ownerDescriptor.Count)
954-
.Select(i => new Pair<int, int>(0, i))
955-
.Concat(itemColumnOffsets.Select(i => new Pair<int, int>(1, i)))
956-
.ToArray(keyFieldCount);
957-
var seekTransform = new MapTransform(true, keyDescriptor, map);
958-
959-
Func<Tuple, Entity> itemCtor = null;
960-
if (association.AuxiliaryType != null) {
961-
itemCtor = DelegateHelper.CreateDelegate<Func<Tuple, Entity>>(null,
962-
association.AuxiliaryType.UnderlyingType, DelegateHelper.AspectedFactoryMethodName,
963-
ArrayUtils<Type>.EmptyArray);
964-
}
965-
966-
return new EntitySetTypeState(seek, seekTransform, itemCtor, entitySet.GetItemCountQueryDelegate(field));
962+
return Session.StorageNode.InternalEntitySetCache.GetOrAdd(Field, BuildEntitySetTypeState, this);
967963
}
968964

969965
private int? GetItemIndex(EntitySetState state, Key key)

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: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,64 +15,73 @@
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 keyColumnsCount = cachingKey.Type.Indexes.PrimaryIndex.KeyColumns.Count;
69+
var keyColumnIndexes = new int[keyColumnsCount];
70+
foreach (var index in Enumerable.Range(0, keyColumnsCount)) {
71+
keyColumnIndexes[index] = index;
72+
}
73+
74+
var columnCollectionLength = cachingKey.Type.Indexes.PrimaryIndex.Columns.Count;
75+
return cachingKey.Type.Indexes.PrimaryIndex.GetQuery().Include(IncludeAlgorithm.ComplexCondition,
76+
true, context => context.GetValue(includeParameter), $"includeColumnName-{Guid.NewGuid()}",
77+
keyColumnIndexes).Filter(t => t.GetValue<bool>(columnCollectionLength)).Select(selectedColumnIndexes);
78+
};
79+
7180
private Dictionary<Key, bool> keys;
7281
private readonly TypeInfo type;
7382
private readonly PrefetchManager manager;
7483
private List<QueryTask> queryTasks;
75-
private readonly CacheKey cacheKey;
84+
private readonly RecordSetCacheKey cacheKey;
7685

7786
public CompilableProvider Provider { get; private set; }
7887

@@ -132,30 +141,12 @@ private QueryTask CreateQueryTask(List<Tuple> currentKeySet)
132141
{
133142
var parameterContext = new ParameterContext();
134143
parameterContext.SetValue(includeParameter, currentKeySet);
135-
object key = new Pair<object, CacheKey>(recordSetCachingRegion, cacheKey);
136-
Func<object, object> generator = CreateRecordSet;
137144
var session = manager.Owner.Session;
138-
Provider = (CompilableProvider) session.StorageNode.InternalQueryCache.GetOrAdd(key, generator);
145+
Provider = session.StorageNode.InternalRecordSetCache.GetOrAdd(cacheKey, CreateRecordSet);
139146
var executableProvider = session.Compile(Provider);
140147
return new QueryTask(executableProvider, session.GetLifetimeToken(), parameterContext);
141148
}
142149

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-
159150
private void PutLoadedStatesInCache(IEnumerable<Tuple> queryResult, EntityDataReader reader,
160151
HashSet<Key> foundedKeys)
161152
{
@@ -221,7 +212,7 @@ public EntityGroupTask(TypeInfo type, int[] columnIndexes, PrefetchManager manag
221212
}
222213

223214
cachedHashCode ^= type.GetHashCode();
224-
cacheKey = new CacheKey(columnIndexes, type, cachedHashCode);
215+
cacheKey = new RecordSetCacheKey(columnIndexes, type, cachedHashCode);
225216
}
226217
}
227218
}

0 commit comments

Comments
 (0)