Skip to content

Commit 55e06d3

Browse files
Double query translation (#1547)
* Avoid re-translating NhLinqExpression. NhLinqExpression instances may be translated multiple times in two cases: * When they have list parameters * When their plan is not cacheable, since it is looked-up two times in cache per execution, and (re)generated in case of cache miss. * Fix a wrong parameter value for query translation It was not having any impact because concrete used implementations are currently not using its value. This introduces in some cases an unneeded ast tree cloning (see ParameterExpander.Expand and #1547 comments on ExpressionQueryImpl), but performances are still better overall.
1 parent f693cc8 commit 55e06d3

File tree

7 files changed

+592
-8
lines changed

7 files changed

+592
-8
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)