Skip to content

Commit e4c5d27

Browse files
authored
Optimization: Make ColumnCollection readonly struct (#352)
1 parent 70f87dc commit e4c5d27

File tree

12 files changed

+99
-166
lines changed

12 files changed

+99
-166
lines changed

Orm/Xtensive.Orm.SqlServer/Orm.Providers.SqlServer/SqlCompiler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected override SqlProvider VisitFreeText(FreeTextProvider provider)
3535

3636
var index = provider.PrimaryIndex.Resolve(Handlers.Domain.Model);
3737
var table = Mapping[index.ReflectedType];
38-
var columns = provider.Header.Columns.Select(column => column.Name).ToList();
38+
var columns = provider.Header.Columns.Columns.Select(column => column.Name).ToList();
3939

4040
if (provider.TopN == null) {
4141
fromTable = SqlDml.FreeTextTable(table, criteriaBinding.ParameterReference, columns);
@@ -66,7 +66,7 @@ protected override SqlProvider VisitContainsTable(ContainsTableProvider provider
6666

6767
var index = provider.PrimaryIndex.Resolve(Handlers.Domain.Model);
6868
var table = Mapping[index.ReflectedType];
69-
var columns = provider.Header.Columns.Select(column => column.Name).ToList();
69+
var columns = provider.Header.Columns.Columns.Select(column => column.Name).ToList();
7070

7171
var targetColumnNames = provider.TargetColumns.Select(c => c.Name).ToArray();
7272
if (provider.TopN == null) {

Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ internal protected override SqlProvider VisitStore(StoreProvider provider)
420420
var source = provider.Source is RawProvider rawProvider
421421
? (ExecutableProvider) (new Rse.Providers.ExecutableRawProvider(rawProvider))
422422
: Compile(provider.Source);
423-
var columnNames = provider.Header.Columns.Select(column => column.Name).ToArray();
423+
var columnNames = provider.Header.Columns.Columns.Select(column => column.Name).ToArray();
424424
var descriptor = DomainHandler.TemporaryTableManager
425425
.BuildDescriptor(Mapping, provider.Name, provider.Header.TupleDescriptor, columnNames);
426426
var request = CreateQueryRequest(Driver, descriptor.QueryStatement, null, descriptor.TupleDescriptor, QueryRequestOptions.Empty);
@@ -553,7 +553,7 @@ internal protected override SqlProvider VisitRowNumber(RowNumberProvider provide
553553

554554
var query = ExtractSqlSelect(provider, source);
555555
var rowNumber = SqlDml.RowNumber();
556-
query.Columns.Add(rowNumber, header.Columns.Last().Name);
556+
query.Columns.Add(rowNumber, header.Columns.Columns.Last().Name);
557557
var columns = ExtractColumnExpressions(query);
558558
foreach (var order in directionCollection)
559559
rowNumber.OrderBy.Add(columns[order.Key], order.Value==Direction.Positive);
@@ -592,7 +592,7 @@ internal protected override SqlProvider VisitLock(LockProvider provider)
592592

593593
protected override void Initialize()
594594
{
595-
foreach (var column in RootProvider.Header.Columns)
595+
foreach (var column in RootProvider.Header.Columns.Columns)
596596
rootColumns.Add(column.Origin);
597597
}
598598

Orm/Xtensive.Orm/Orm/Rse/ColumnCollection.cs

Lines changed: 71 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -4,154 +4,91 @@
44
// Created by: Alexey Kochetov
55
// Created: 2007.09.24
66

7-
using System;
8-
using System.Collections;
9-
using System.Collections.Generic;
10-
using System.Linq;
117
using Xtensive.Core;
128

9+
namespace Xtensive.Orm.Rse;
1310

14-
namespace Xtensive.Orm.Rse
11+
/// <summary>
12+
/// Collection of <see cref="Column"/> items.
13+
/// </summary>
14+
[Serializable]
15+
public readonly struct ColumnCollection
1516
{
16-
/// <summary>
17-
/// Collection of <see cref="Column"/> items.
18-
/// </summary>
19-
[Serializable]
20-
public sealed class ColumnCollection : IReadOnlyList<Column>
21-
{
22-
public struct Enumerator : IEnumerator<Column>, IEnumerator
23-
{
24-
private readonly IReadOnlyList<Column> list;
25-
private int index;
26-
public Column Current { get; private set; }
27-
28-
object IEnumerator.Current =>
29-
index == 0 || index == list.Count + 1
30-
? throw new InvalidOperationException("Enumeration cannot happen")
31-
: Current;
32-
33-
public void Dispose()
34-
{
35-
}
36-
37-
public bool MoveNext()
38-
{
39-
if (index < list.Count) {
40-
Current = list[index++];
41-
return true;
42-
}
43-
index = list.Count + 1;
44-
Current = default;
45-
return false;
46-
}
47-
48-
void IEnumerator.Reset()
49-
{
50-
index = 0;
51-
Current = default;
52-
}
53-
54-
internal Enumerator(IReadOnlyList<Column> list)
55-
{
56-
this.list = list;
57-
index = 0;
58-
Current = default;
59-
}
60-
}
17+
private readonly Dictionary<string, int> nameIndex;
6118

62-
private readonly Dictionary<string, int> nameIndex;
63-
private readonly IReadOnlyList<Column> columns;
19+
public IReadOnlyList<Column> Columns { get; }
6420

65-
/// <summary>
66-
/// Gets the number of <see href="Column"/>s in the collection.
67-
/// </summary>
68-
public ColNum Count => (ColNum)columns.Count;
69-
70-
int IReadOnlyCollection<Column>.Count => columns.Count;
71-
72-
/// <summary>
73-
/// Gets a <see href="Column"/> instance by its index.
74-
/// </summary>
75-
public Column this[int index] => columns[index];
76-
77-
/// <summary>
78-
/// Gets <see cref="Column"/> by provided <paramref name="fullName"/>.
79-
/// </summary>
80-
/// <remarks>
81-
/// Returns <see cref="Column"/> if it was found; otherwise <see langword="null"/>.
82-
/// </remarks>
83-
/// <param name="fullName">Full name of the <see cref="Column"/> to find.</param>
84-
public Column this[string fullName] =>
85-
nameIndex.TryGetValue(fullName, out var index) ? columns[index] : null;
21+
/// <summary>
22+
/// Gets the number of <see href="Column"/>s in the collection.
23+
/// </summary>
24+
public ColNum Count => (ColNum)Columns.Count;
8625

87-
/// <summary>
88-
/// Determines whether the collecton contains specified column
89-
/// </summary>
90-
/// <param name="column"></param>
91-
/// <returns></returns>
92-
public bool Contains(Column column)
93-
{
94-
if (columns is ICollection<Column> colColumns)
95-
return colColumns.Contains(column);
96-
return columns.Contains(column);
97-
}
9826

99-
/// <summary>
100-
/// Joins this collection with specified the column collection.
101-
/// </summary>
102-
/// <param name="joined">The joined.</param>
103-
/// <returns>The joined collection.</returns>
104-
public ColumnCollection Join(IEnumerable<Column> joined)
105-
{
106-
return new ColumnCollection(this.Concat(joined).ToList());
107-
}
27+
/// <summary>
28+
/// Gets a <see href="Column"/> instance by its index.
29+
/// </summary>
30+
public Column this[int index] => Columns[index];
10831

109-
/// <summary>
110-
/// Aliases the specified <see cref="Column"/> collection.
111-
/// </summary>
112-
/// <param name="alias">The alias to add.</param>
113-
/// <returns>Aliased collection of columns.</returns>
114-
public ColumnCollection Alias(string alias)
115-
{
116-
ArgumentException.ThrowIfNullOrEmpty(alias);
117-
return new ColumnCollection(this.Select(column => column.Clone(alias + "." + column.Name)).ToArray(Count));
118-
}
32+
/// <summary>
33+
/// Gets <see cref="Column"/> by provided <paramref name="fullName"/>.
34+
/// </summary>
35+
/// <remarks>
36+
/// Returns <see cref="Column"/> if it was found; otherwise <see langword="null"/>.
37+
/// </remarks>
38+
/// <param name="fullName">Full name of the <see cref="Column"/> to find.</param>
39+
public Column this[string fullName] =>
40+
nameIndex.TryGetValue(fullName, out var index) ? Columns[index] : null;
11941

120-
/// <summary>
121-
/// Returns an enumerator that iterates through the <see href="ColumnCollection"/>.
122-
/// </summary>
123-
public Enumerator GetEnumerator() => new Enumerator(this);
42+
/// <summary>
43+
/// Determines whether the collecton contains specified column
44+
/// </summary>
45+
/// <param name="column"></param>
46+
/// <returns></returns>
47+
public bool Contains(Column column) =>
48+
Columns is ICollection<Column> colColumns ? colColumns.Contains(column) : Columns.Contains(column);
12449

125-
/// <summary>
126-
/// Returns an enumerator that iterates through the <see href="ColumnCollection"/>.
127-
/// </summary>
128-
IEnumerator<Column> IEnumerable<Column>.GetEnumerator() => GetEnumerator();
50+
/// <summary>
51+
/// Joins this collection with specified the column collection.
52+
/// </summary>
53+
/// <param name="joined">The joined.</param>
54+
/// <returns>The joined collection.</returns>
55+
public ColumnCollection Join(IEnumerable<Column> joined)
56+
{
57+
return new ColumnCollection(Columns.Concat(joined).ToList());
58+
}
12959

130-
/// <inheritdoc/>
131-
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
60+
/// <summary>
61+
/// Aliases the specified <see cref="Column"/> collection.
62+
/// </summary>
63+
/// <param name="alias">The alias to add.</param>
64+
/// <returns>Aliased collection of columns.</returns>
65+
public ColumnCollection Alias(string alias)
66+
{
67+
ArgumentException.ThrowIfNullOrEmpty(alias);
68+
return new ColumnCollection(Columns.Select(column => column.Clone(alias + "." + column.Name)).ToArray(Count));
69+
}
13270

133-
// Constructors
71+
// Constructors
13472

135-
/// <summary>
136-
/// Initializes a new instance of this class.
137-
/// </summary>
138-
/// <param name="columns">Collection of items to add.</param>
139-
/// <remarks>
140-
/// <paramref name="columns"/> is used to initialize inner field directly
141-
/// to save time on avoiding collection copy. If you pass an <see cref="IReadOnlyList{Column}"/>
142-
/// implementor that, in fact, can be changed, make sure the passed collection doesn't change afterwards.
143-
/// Ideally, use arrays instead of <see cref="List{T}"/> or similar collections.
144-
/// Changing the passed collection afterwards will lead to unpredictable results.
145-
/// </remarks>
146-
public ColumnCollection(IReadOnlyList<Column> columns)
147-
{
148-
//!!! Direct initialization by parameter is unsafe performance optimization: See remarks in ctor summary!
149-
this.columns = columns;
150-
var count = this.columns.Count;
151-
nameIndex = new Dictionary<string, int>(count);
152-
for (var index = count; index-- > 0;) {
153-
nameIndex.Add(this.columns[index].Name, index);
154-
}
73+
/// <summary>
74+
/// Initializes a new instance of this class.
75+
/// </summary>
76+
/// <param name="columns">Collection of items to add.</param>
77+
/// <remarks>
78+
/// <paramref name="columns"/> is used to initialize inner field directly
79+
/// to save time on avoiding collection copy. If you pass an <see cref="IReadOnlyList{Column}"/>
80+
/// implementor that, in fact, can be changed, make sure the passed collection doesn't change afterwards.
81+
/// Ideally, use arrays instead of <see cref="List{T}"/> or similar collections.
82+
/// Changing the passed collection afterwards will lead to unpredictable results.
83+
/// </remarks>
84+
public ColumnCollection(IReadOnlyList<Column> columns)
85+
{
86+
//!!! Direct initialization by parameter is unsafe performance optimization: See remarks in ctor summary!
87+
Columns = columns;
88+
var count = Columns.Count;
89+
nameIndex = new Dictionary<string, int>(count);
90+
for (var index = count; index-- > 0;) {
91+
nameIndex.Add(Columns[index].Name, index);
15592
}
15693
}
15794
}

Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/SelectProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected override RecordSetHeader BuildHeader()
3232
/// <inheritdoc/>
3333
protected override string ParametersToString()
3434
{
35-
return Header.Columns.Select(c => c.Name).ToCommaDelimitedString();
35+
return Header.Columns.Columns.Select(c => c.Name).ToCommaDelimitedString();
3636
}
3737

3838
internal override Provider Visit(ProviderVisitor visitor) => visitor.VisitSelect(this);
@@ -50,4 +50,4 @@ public SelectProvider(CompilableProvider provider, IReadOnlyList<ColNum> columnI
5050
Initialize();
5151
}
5252
}
53-
}
53+
}

Orm/Xtensive.Orm/Orm/Rse/RecordSetHeader.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public RecordSetHeader Alias(string alias)
6464
ColumnCollection resultColumns = Columns.Alias(alias);
6565

6666
return new RecordSetHeader(
67-
TupleDescriptor, resultColumns, ColumnGroups, OrderTupleDescriptor, Order);
67+
TupleDescriptor, resultColumns.Columns, ColumnGroups, OrderTupleDescriptor, Order);
6868
}
6969

7070
/// <summary>
@@ -75,7 +75,7 @@ public RecordSetHeader Alias(string alias)
7575
public RecordSetHeader Add(Column column)
7676
{
7777
var newColumns = new List<Column>(Columns.Count + 1);
78-
newColumns.AddRange(Columns);
78+
newColumns.AddRange(Columns.Columns);
7979
newColumns.Add(column);
8080

8181
var newTupleDescriptor = CreateTupleDescriptor(newColumns);
@@ -96,7 +96,7 @@ public RecordSetHeader Add(Column column)
9696
public RecordSetHeader Add(IReadOnlyList<Column> columns)
9797
{
9898
var n = Columns.Count + columns.Count;
99-
var newColumns = Columns.Concat(columns).ToArray(n);
99+
var newColumns = Columns.Columns.Concat(columns).ToArray(n);
100100

101101
var newTupleDescriptor = CreateTupleDescriptor(newColumns);
102102

@@ -118,10 +118,10 @@ public RecordSetHeader Join(RecordSetHeader joined)
118118
var columnCount = Columns.Count;
119119
var newColumns = new Column[columnCount + joined.Columns.Count];
120120
int j = 0;
121-
foreach (var c in Columns) {
121+
foreach (var c in Columns.Columns) {
122122
newColumns[j++] = c;
123123
}
124-
foreach (var c in joined.Columns) {
124+
foreach (var c in joined.Columns.Columns) {
125125
newColumns[j++] = c.Clone((ColNum) (columnCount + c.Index));
126126
}
127127

@@ -201,7 +201,7 @@ public RecordSetHeader Sort(DirectionCollection<ColNum> sortOrder)
201201
{
202202
return new RecordSetHeader(
203203
TupleDescriptor,
204-
Columns,
204+
Columns.Columns,
205205
ColumnGroups,
206206
TupleDescriptor,
207207
sortOrder);
@@ -269,7 +269,7 @@ private static TupleDescriptor CreateTupleDescriptor(IReadOnlyList<Column> newCo
269269
/// <inheritdoc/>
270270
public override string ToString()
271271
{
272-
return Columns.Select(c => c.ToString()).ToCommaDelimitedString();
272+
return Columns.Columns.Select(c => c.ToString()).ToCommaDelimitedString();
273273
}
274274

275275

Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ internal protected override IncludeProvider VisitInclude(IncludeProvider provide
4242
var source = VisitCompilable(provider.Source);
4343

4444
var currentMapping = mappings[provider.Source];
45-
var calulatedColumn = provider.Header.Columns.Last();
45+
var calulatedColumn = provider.Header.Columns.Columns.Last();
4646
mappings[provider] = Merge(currentMapping, [calulatedColumn.Index]);
4747
if (source == provider.Source) {
4848
return provider;
@@ -260,7 +260,7 @@ internal protected override AggregateProvider VisitAggregate(AggregateProvider p
260260
using ColumnMap sourceMap = new(mappings[provider.Source]);
261261
var currentMap = mappings[provider];
262262

263-
mappings[provider] = provider.Header.Columns.Select(c => c.Index).ToList(provider.Header.Columns.Count);
263+
mappings[provider] = provider.Header.Columns.Columns.Select(c => c.Index).ToList(provider.Header.Columns.Count);
264264

265265
if (source == provider.Source) {
266266
return provider;
@@ -325,7 +325,7 @@ internal protected override RowNumberProvider VisitRowNumber(RowNumberProvider p
325325
mappings[provider.Source] = mappings[provider].Where(i => i < sourceLength).ToList();
326326
var newSource = VisitCompilable(provider.Source);
327327
var currentMapping = mappings[provider.Source];
328-
var rowNumberColumn = provider.Header.Columns.Last();
328+
var rowNumberColumn = provider.Header.Columns.Columns.Last();
329329
mappings[provider] = Merge(currentMapping, [rowNumberColumn.Index]);
330330
return newSource == provider.Source
331331
? provider
@@ -340,7 +340,7 @@ internal protected override CompilableProvider VisitStore(StoreProvider provider
340340

341341
if (hasGrouping) {
342342
mappings.Add(provider.Sources[0],
343-
Merge(mappings[provider], provider.Header.Columns.Select((c, i) => (ColNum)i)));
343+
Merge(mappings[provider], provider.Header.Columns.Columns.Select((c, i) => (ColNum)i)));
344344
}
345345
else {
346346
OnRecursionEntrance(provider);

0 commit comments

Comments
 (0)