Skip to content

Commit d6de718

Browse files
committed
Search improvements
- Apply alphabetical index over biggest subnamespaces "Issues" and "Upgrade" - small optimizations - majority of models are less than 6 items so window is decreaded, it will affect big models of 100s types but it is already faster - registration extension moved to TestHelper
1 parent 72ff130 commit d6de718

2 files changed

Lines changed: 92 additions & 52 deletions

File tree

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2018 Xtensive LLC.
1+
// Copyright (C) 2018-2025 Xtensive LLC.
22
// All rights reserved.
33
// For conditions of distribution and use, see license.
44
// Created by: Alexey Kulakov
@@ -16,12 +16,17 @@ namespace Xtensive.Orm.Tests
1616
{
1717
public static class AssemblyExtensions
1818
{
19-
private static readonly Type ObjectType = typeof(object);
20-
private static readonly string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests."; // keep the dot at the end
19+
private const string MainTestAsseblyNsPrefix = "Xtensive.Orm.Tests."; // keep the dot at the end
20+
private const string IssuesNsPrefix = "Xtensive.Orm.Tests.Issues.";// keep the dot at the end
21+
private const string UpgradeNsPrefix = "Xtensive.Orm.Tests.Upgrade.";// keep the dot at the end
22+
23+
2124
private static readonly byte[] ThisAssemblyPkt = typeof(AssemblyExtensions).Assembly.GetName().GetPublicKeyToken();
2225

2326
private static readonly ConcurrentDictionary<Assembly, Type[]> TypesPerAssembly = new();
2427
private static readonly ConcurrentDictionary<char, int> XtensiveOrmTestsNsAlphabeticIndex = new();
28+
private static readonly ConcurrentDictionary<char, int> MainTestsForUpgrade = new();
29+
private static readonly ConcurrentDictionary<char, int> MainTestsForIssues = new();
2530

2631
public static System.Configuration.Configuration GetAssemblyConfiguration(this Assembly assembly)
2732
{
@@ -33,7 +38,7 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
3338
if (string.IsNullOrWhiteSpace(@namespace))
3439
throw new ArgumentException("Namespace cannot be null, empty or contains only white spaces");
3540

36-
// these two dummy mentions to not forget to sync filtration algorithm here and in the classes,
41+
// these two dummy mentionsa are to not forget to sync filtration algorithm here and in the classes,
3742
// in particular BaseType property, if the property changed then this algorighm should be changed as well
3843
_ = nameof(Xtensive.IoC.ServiceTypeRegistrationProcessor.BaseType);
3944
_ = nameof(Xtensive.Orm.Configuration.DomainTypeRegistrationHandler.BaseType);
@@ -43,19 +48,30 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
4348

4449
var assemblyTypes = TypesPerAssembly.GetOrAdd(assembly, static (a, isMain) => {
4550
var allTypes = a.GetTypes();
51+
52+
var objectType = typeof(object);
4653
var list = new List<Type>(allTypes.Length);
4754
var currentIndex = 0;
4855
foreach (var t in allTypes) {
4956
// we ignore compiler generated types because usuallty they are
5057
// at the end of sorted types
51-
if (t.IsSubclassOf(ObjectType) && t.GetCustomAttribute<CompilerGeneratedAttribute>() == null) {
58+
if (t.IsSubclassOf(objectType) && t.GetCustomAttribute<CompilerGeneratedAttribute>() == null) {
5259
list.Add(t);
5360
if (isMain) {
54-
if (t.Namespace != null && t.Namespace.StartsWith(MainTestAsseblyNsPrefix, StringComparison.Ordinal)) {
55-
var firstLetter = t.Namespace[MainTestAsseblyNsPrefix.Length];
61+
var nSpace = t.Namespace;
62+
if (nSpace != null && nSpace.StartsWith(MainTestAsseblyNsPrefix, StringComparison.Ordinal)) {
63+
var firstLetter = nSpace[MainTestAsseblyNsPrefix.Length];
5664
// main test library has 5000+ types, to not enumerate them every type from the beginning
5765
// we try to have parts by first letter
5866
_ = XtensiveOrmTestsNsAlphabeticIndex.TryAdd(firstLetter, currentIndex);
67+
if (firstLetter == 'I'/*ssue*/ && nSpace.StartsWith(IssuesNsPrefix)) {
68+
var firstIssuesLetter = nSpace[IssuesNsPrefix.Length];
69+
_ = MainTestsForIssues.TryAdd(firstIssuesLetter, currentIndex);
70+
}
71+
if (firstLetter== 'U'/*pdate*/ && nSpace.StartsWith(UpgradeNsPrefix)) {
72+
var firstIssuesLetter = nSpace[UpgradeNsPrefix.Length];
73+
_ = MainTestsForUpgrade.TryAdd(firstIssuesLetter, currentIndex);
74+
}
5975
}
6076
}
6177
currentIndex++;
@@ -64,71 +80,81 @@ public static IReadOnlyList<Type> GetTypesFromNamespaceCaching(this Assembly ass
6480
return list.ToArray();
6581
}, isMainTestAssembly);
6682

67-
var range = FindRange(assemblyTypes, @namespace, isMainTestAssembly);
68-
return new ArraySegment<Type>(assemblyTypes, range.first, range.last - range.first + 1);
69-
70-
71-
//type.IsSubclassOf(BaseType) && (ns.IsNullOrEmpty() || (type.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0));
83+
return FindSegment(assemblyTypes, @namespace, isMainTestAssembly);
7284
}
7385

74-
private static (int first, int last) FindRange(Type[] types, string ns, bool isMainAssembly)
86+
private static int GetSearchStartPosition(string ns, bool isMainAssembly)
7587
{
76-
const int windowSize = 10;
88+
var searchStart = 0;
89+
if (isMainAssembly) {
90+
if (ns.StartsWith(IssuesNsPrefix)) {
91+
searchStart = MainTestsForIssues[ns[IssuesNsPrefix.Length]];
92+
}
93+
else if (ns.StartsWith(UpgradeNsPrefix)) {
94+
searchStart = MainTestsForUpgrade[ns[UpgradeNsPrefix.Length]];
95+
}
96+
else if (ns.StartsWith(MainTestAsseblyNsPrefix)) {
97+
searchStart = XtensiveOrmTestsNsAlphabeticIndex[ns[MainTestAsseblyNsPrefix.Length]];
98+
}
99+
}
77100

78-
var searchStart = (isMainAssembly)
79-
? (ns.StartsWith(MainTestAsseblyNsPrefix))
80-
? XtensiveOrmTestsNsAlphabeticIndex[ns[MainTestAsseblyNsPrefix.Length]]
81-
: 0 //types from root namespace
82-
: 0;
101+
return searchStart;
102+
}
83103

84-
// we rely on the fact that types are sorted by full name, that means types of same namespace are go one by one
85-
// which gives us to optimize search - we find first type that has desired namespace, then we try to find last one
86-
// and then we return the part of original array as result
104+
private static int FindFirstEntry(Type[] types, in int serachFrom, in string nsAndDot)
105+
{
87106
var firstHit = -1;
88-
var lastHit = -1;
89107

90-
for (int headIndex = searchStart, count = types.Length ; headIndex < count; headIndex++) {
108+
for (int headIndex = serachFrom, count = types.Length; headIndex < count; headIndex++) {
91109
var head = types[headIndex];
92-
if (head.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0) {
110+
if (head.FullName.IndexOf(nsAndDot, StringComparison.InvariantCulture) >= 0) {
93111
firstHit = headIndex;
94112
break;
95113
}
96114
}
97115

98-
var isOutOfRange = false;
99-
lastHit = firstHit;
116+
if (firstHit == -1)
117+
throw new Exception($"There is no any entry for fiven namespace.");
118+
119+
return firstHit;
120+
}
121+
122+
private static IReadOnlyList<Type> FindSegment(Type[] types, string ns, bool isMainAssembly)
123+
{
124+
// We rely on the fact that types are sorted by full name.
125+
// That opens an opportunity to optimize search of types from given
126+
// namespace and subnamespaces.
127+
128+
const int windowSize = 6;
129+
130+
var searchFrom = GetSearchStartPosition(ns, isMainAssembly);
131+
132+
var nsAndDot = ns + ".";
133+
var startSearchBoundary = FindFirstEntry(types, searchFrom, nsAndDot);
134+
135+
var wrongNsFound = false;
136+
var endSearchBoundary = startSearchBoundary;
137+
var lastTypeIndex = types.Length - 1;
100138
do {
101-
lastHit = lastHit + windowSize;
102-
if (lastHit > types.Length - 1) {
103-
lastHit = types.Length - 1;
104-
}
105-
var tail = types[lastHit];
106-
if (tail.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) < 0)
107-
isOutOfRange = true;
108-
if (lastHit < firstHit)
109-
throw new Exception("There is something strage in the neighborhood! :-)");
139+
endSearchBoundary += windowSize;
140+
if (endSearchBoundary > lastTypeIndex)
141+
endSearchBoundary = lastTypeIndex;
142+
143+
var tail = types[endSearchBoundary];
144+
if (tail.FullName.IndexOf(nsAndDot, StringComparison.InvariantCulture) < 0)
145+
wrongNsFound = true;
110146
}
111-
while (!isOutOfRange);
147+
while (!wrongNsFound || endSearchBoundary < lastTypeIndex);
112148

113-
for (int tailIndex = lastHit; tailIndex >= firstHit; tailIndex--) {
149+
for (var tailIndex = endSearchBoundary; tailIndex >= startSearchBoundary; tailIndex--) {
114150
var tail = types[tailIndex];
115-
lastHit = tailIndex;
116-
if (tail.FullName.IndexOf(ns + ".", StringComparison.InvariantCulture) >= 0) {
151+
endSearchBoundary = tailIndex;
152+
if (tail.FullName.IndexOf(nsAndDot, StringComparison.InvariantCulture) >= 0) {
117153
break;
118154
}
119155
}
120156

121-
return (firstHit, lastHit);
122-
}
123-
}
124-
125-
public static class TypeRegistryExtensions
126-
{
127-
public static void RegisterCaching(this Xtensive.Orm.Configuration.DomainTypeRegistry _this, Assembly assembly, string @namespace)
128-
{
129-
foreach (var t in assembly.GetTypesFromNamespaceCaching(@namespace)) {
130-
_this.Register(t);
131-
}
157+
return new ArraySegment<Type>(types, startSearchBoundary, endSearchBoundary - startSearchBoundary + 1);
132158
}
133159
}
134160
}

Orm/Xtensive.Orm.Tests.Framework/TestHelper.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright (C) 2008-2021 Xtensive LLC.
1+
// Copyright (C) 2008-2025 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: Alex Yakunin
55
// Created: 2008.02.09
66

77
using System;
8+
using System.Reflection;
89
using System.Threading;
910

1011
namespace Xtensive.Orm.Tests
@@ -82,5 +83,18 @@ public static DateTime FixDateTimeForProvider(this DateTime origin, StorageProvi
8283
var newTicks = ticks - (ticks % divider.Value);
8384
return new DateTime(newTicks);
8485
}
86+
87+
/// <summary>
88+
/// Optimized Domain types registration of types from certain namespace.
89+
/// </summary>
90+
/// <param name="registry"><see cref="Configuration.DomainConfiguration.Types"/> registry</param>
91+
/// <param name="assembly">Assembly which types should be registered</param>
92+
/// <param name="namespace">Namespace within <paramref name="assembly"/>types should be registered.</param>
93+
public static void RegisterCaching(this Xtensive.Orm.Configuration.DomainTypeRegistry registry, Assembly assembly, string @namespace)
94+
{
95+
foreach (var t in assembly.GetTypesFromNamespaceCaching(@namespace)) {
96+
registry.Register(t);
97+
}
98+
}
8599
}
86100
}

0 commit comments

Comments
 (0)