Skip to content

Commit eb10b68

Browse files
Add a benchmark of LINQ queries impacted by the change.
1 parent aeaba33 commit eb10b68

File tree

4 files changed

+556
-0
lines changed

4 files changed

+556
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Data;
14+
using System.Data.Common;
15+
using System.Diagnostics;
16+
using System.Linq;
17+
using System.Reflection;
18+
using NHibernate.Cfg;
19+
using NHibernate.Driver;
20+
using NHibernate.Engine;
21+
using NHibernate.Engine.Query;
22+
using NHibernate.SqlCommand;
23+
using NHibernate.SqlTypes;
24+
using NHibernate.Util;
25+
using NSubstitute;
26+
using NUnit.Framework;
27+
28+
namespace NHibernate.Test.NHSpecificTest.GH1547
29+
{
30+
using System.Threading.Tasks;
31+
using System.Threading;
32+
[TestFixture, Explicit("Contains only performances benchmark")]
33+
public class FixtureAsync : BugTestCase
34+
{
35+
protected override void Configure(Configuration configuration)
36+
{
37+
base.Configure(configuration);
38+
39+
var driverClass = ReflectHelper.ClassForName(configuration.GetProperty(Cfg.Environment.ConnectionDriver));
40+
DriverForSubstitutedCommand.DriverClass = driverClass;
41+
42+
configuration.SetProperty(
43+
Cfg.Environment.ConnectionDriver,
44+
typeof(DriverForSubstitutedCommand).AssemblyQualifiedName);
45+
}
46+
47+
protected override void DropSchema()
48+
{
49+
(Sfi.ConnectionProvider.Driver as DriverForSubstitutedCommand)?.CleanUp();
50+
base.DropSchema();
51+
}
52+
53+
[Test]
54+
public Task SimpleLinqPerfAsync()
55+
{
56+
return BenchmarkAsync(
57+
"Simple LINQ",
58+
s =>
59+
s
60+
.Query<Entity>()
61+
.Where(e => e.Name == "Bob"));
62+
}
63+
64+
[Test]
65+
public Task LinqWithNonParameterizedConstantPerfAsync()
66+
{
67+
return BenchmarkAsync(
68+
"Non parameterized constant",
69+
s =>
70+
s
71+
.Query<Entity>()
72+
.Where(e => e.Name == "Bob")
73+
.Select(e => new { e, c = 2 }));
74+
}
75+
76+
[Test]
77+
public Task LinqWithListParameterPerfAsync()
78+
{
79+
try
80+
{
81+
var names = new[] { "Bob", "Sally" };
82+
return BenchmarkAsync(
83+
"List parameter",
84+
s =>
85+
s
86+
.Query<Entity>()
87+
.Where(e => names.Contains(e.Name)));
88+
}
89+
catch (Exception ex)
90+
{
91+
return Task.FromException<object>(ex);
92+
}
93+
}
94+
95+
private async Task BenchmarkAsync<T>(string test, Func<ISession, IQueryable<T>> queryFactory, CancellationToken cancellationToken = default(CancellationToken))
96+
{
97+
var driver = (DriverForSubstitutedCommand) Sfi.ConnectionProvider.Driver;
98+
var timings = new List<double>();
99+
var sw = new Stopwatch();
100+
101+
var cache = (SoftLimitMRUCache)
102+
typeof(QueryPlanCache)
103+
.GetField("planCache", BindingFlags.Instance | BindingFlags.NonPublic)
104+
.GetValue(Sfi.QueryPlanCache);
105+
106+
using (var session = OpenSession())
107+
using (var tx = session.BeginTransaction())
108+
{
109+
using (driver.SubstituteCommand())
110+
{
111+
var query = queryFactory(session);
112+
// Warm up.
113+
await (RunBenchmarkUnitAsync(cache, query));
114+
115+
for (var j = 0; j < 1000; j++)
116+
{
117+
sw.Restart();
118+
await (RunBenchmarkUnitAsync(cache, query));
119+
sw.Stop();
120+
timings.Add(sw.Elapsed.TotalMilliseconds);
121+
}
122+
}
123+
124+
await (tx.CommitAsync(cancellationToken));
125+
}
126+
127+
var avg = timings.Average();
128+
Console.WriteLine(
129+
$"{test} average time: {avg}ms (s {Math.Sqrt(timings.Sum(t => Math.Pow(t - avg, 2)) / (timings.Count - 1))}ms)");
130+
}
131+
132+
private static Task RunBenchmarkUnitAsync<T>(SoftLimitMRUCache cache, IQueryable<T> query)
133+
{
134+
try
135+
{
136+
// Do enough iterations for having a significant elapsed time in milliseconds.
137+
for (var i = 0; i < 20; i++)
138+
{
139+
// Always clear the query plan cache before running the query, otherwise the impact of 1547
140+
// change would be hidden by it. Simulates having many different queries run.
141+
cache.Clear();
142+
Assert.That(query.ToList, Throws.Nothing);
143+
}
144+
return Task.CompletedTask;
145+
}
146+
catch (Exception ex)
147+
{
148+
return Task.FromException<object>(ex);
149+
}
150+
}
151+
}
152+
153+
public partial class DriverForSubstitutedCommand : IDriver
154+
{
155+
156+
#region Firebird mess
157+
158+
#endregion
159+
#region Pure forwarding
160+
161+
#endregion
162+
163+
private partial class SubstituteDbCommand : DbCommand
164+
{
165+
166+
protected override Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
167+
{
168+
try
169+
{
170+
return Task.FromResult<DbDataReader>(_substituteReader);
171+
}
172+
catch (Exception ex)
173+
{
174+
return Task.FromException<DbDataReader>(ex);
175+
}
176+
}
177+
178+
public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
179+
{
180+
return Task.FromResult<int>(0);
181+
}
182+
183+
public override Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)
184+
{
185+
return Task.FromResult<object>(null);
186+
}
187+
188+
#region Pure forwarding
189+
190+
#endregion
191+
}
192+
}
193+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH1547
4+
{
5+
class Entity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)