-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Nextorm to benchmarks #2025
base: main
Are you sure you want to change the base?
Changes from 10 commits
0d6b314
54cf189
2cf7c35
542aa42
a2dccdb
41b90e3
b602660
44af8c0
8f25eab
de1da59
902799f
89974af
48d8ab8
eacedf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using BenchmarkDotNet.Attributes; | ||
using DevExpress.Data.Access; | ||
using System.Data.SqlClient; | ||
using Microsoft.Extensions.Logging; | ||
using nextorm.core; | ||
using nextorm.sqlserver; | ||
|
||
namespace Dapper.Tests.Performance; | ||
|
||
[Description("Nextorm")] | ||
public class NextormBenchmarks : BenchmarkBase | ||
{ | ||
private NextormRepository _repository; | ||
private QueryCommand<Post> _getPostByIdCompiled; | ||
private QueryCommand<Post> _getPostById; | ||
private QueryCommand<Post> _queryBufferedCompiled; | ||
private QueryCommand<Post> _queryUnbufferedCompiled; | ||
|
||
[GlobalSetup] | ||
public void GlobalSetup() => Setup(false); | ||
public void Setup(bool withLogging) | ||
{ | ||
BaseSetup(); | ||
var builder = new DbContextBuilder(); | ||
builder.UseSqlServer(_connection); | ||
if (withLogging) | ||
{ | ||
var logFactory = LoggerFactory.Create(config => config.AddConsole().SetMinimumLevel(LogLevel.Debug)); | ||
builder.UseLoggerFactory(logFactory); | ||
builder.LogSensitiveData(true); | ||
} | ||
|
||
_repository = new NextormRepository(builder); | ||
|
||
var cmdBuilder = _repository.Posts.Where(it => it.Id == NORM.Param<int>(0)); | ||
_queryBufferedCompiled = cmdBuilder.ToCommand().Compile(); | ||
_queryUnbufferedCompiled = cmdBuilder.ToCommand().Compile(false); | ||
_getPostById = cmdBuilder.FirstOrFirstOrDefaultCommand(); | ||
_getPostByIdCompiled = _getPostById.Compile(); | ||
} | ||
[Benchmark(Description = "First")] | ||
public Post First() | ||
{ | ||
Step(); | ||
return _repository.Posts.Where(it => it.Id == i).FirstOrDefault(); | ||
} | ||
[Benchmark(Description = "Query<T> (buffered)")] | ||
public Post QueryBuffered() | ||
{ | ||
Step(); | ||
return _repository.Posts.Where(it => it.Id == i).ToList().FirstOrDefault(); | ||
} | ||
[Benchmark(Description = "Query<T> (unbuffered)")] | ||
public Post QueryUnbuffered() | ||
{ | ||
Step(); | ||
return _repository.Posts.Where(it => it.Id == i).AsEnumerable().FirstOrDefault(); | ||
} | ||
[Benchmark(Description = "First with param")] | ||
public Post FirstParam() | ||
{ | ||
Step(); | ||
return _getPostById.FirstOrDefault(i); | ||
} | ||
|
||
[Benchmark(Description = "First compiled")] | ||
public Post FirstCompiled() | ||
{ | ||
Step(); | ||
return _getPostByIdCompiled.FirstOrDefault(i); | ||
} | ||
[Benchmark(Description = "Query<T> (compiled buffered)")] | ||
public Post QueryBufferedCompiled() | ||
{ | ||
Step(); | ||
return _queryBufferedCompiled.ToList(i).FirstOrDefault(); | ||
} | ||
[Benchmark(Description = "Query<T> (compiled unbuffered)")] | ||
public Post QueryUnbufferedCompiled() | ||
{ | ||
Step(); | ||
return _queryUnbufferedCompiled.AsEnumerable(i).FirstOrDefault(); | ||
} | ||
} | ||
|
||
public class NextormRepository | ||
{ | ||
public NextormRepository(DbContextBuilder builder) : this(builder.CreateDbContext()) | ||
{ | ||
} | ||
public NextormRepository(IDataContext dataContext) | ||
{ | ||
Posts = dataContext.Create<Post>(config => config.Table("posts")); | ||
} | ||
|
||
public Entity<Post> Posts { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
<AssemblyName>Dapper.Tests.Performance</AssemblyName> | ||
<Description>Dapper Core Performance Suite</Description> | ||
<OutputType>Exe</OutputType> | ||
<TargetFrameworks>net462;net5.0;net8.0</TargetFrameworks> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure we want to change this - what's the intent here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're right, removed |
||
<TargetFrameworks>net462;net8.0</TargetFrameworks> | ||
<IsTestProject>false</IsTestProject> | ||
<NoWarn>$(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060</NoWarn> | ||
|
@@ -14,7 +15,7 @@ | |
<PackageReference Include="Belgrade.Sql.Client" /> | ||
<PackageReference Include="BenchmarkDotNet" /> | ||
<PackageReference Include="DevExpress.Xpo" /> | ||
<PackageReference Include="EntityFramework" VersionOverride="6.4.4"/> | ||
<PackageReference Include="EntityFramework" VersionOverride="6.4.4" /> | ||
<PackageReference Include="FirebirdSql.Data.FirebirdClient" /> | ||
<PackageReference Include="linq2db.SqlServer" /> | ||
<PackageReference Include="MySqlConnector" /> | ||
|
@@ -33,7 +34,7 @@ | |
<EmbeddedResource Include="NHibernate\*.xml" /> | ||
<Compile Update="Benchmarks.*.cs" DependentUpon="Benchmarks.cs" /> | ||
</ItemGroup> | ||
|
||
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'"> | ||
<DefineConstants>$(DefineConstants);NET4X</DefineConstants> | ||
</PropertyGroup> | ||
|
@@ -46,9 +47,20 @@ | |
<Reference Include="Microsoft.CSharp" /> | ||
<Reference Include="System.Configuration" /> | ||
<Reference Include="System.Data.Linq" /> | ||
<Compile Remove="Benchmarks.Nextorm.cs" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather use a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
</ItemGroup> | ||
<ItemGroup Condition="'$(TargetFramework)' != 'net462'"> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" /> | ||
<PackageReference Include="Norm.net" /> | ||
</ItemGroup> | ||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'"> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" VersionOverride="5.0.17" /> | ||
<Compile Remove="Benchmarks.Nextorm.cs" /> | ||
</ItemGroup> | ||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> | ||
<PackageReference Include="nextorm.sqlserver" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" VersionOverride="8.0.0" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need all these version-overrides? that seems unlikely There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've got this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that probably means it needs adding to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
<PackageReference Include="Microsoft.Extensions.Logging.Console" VersionOverride="8.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.ObjectPool" VersionOverride="8.0.0" /> | ||
<PackageReference Include="OneOf" VersionOverride="3.0.263" /> | ||
</ItemGroup> | ||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this actually realistic usage? usually, you don't want to
Compile()
anything per-context-instance, as the context-instance is going to be transient - is this different here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_queryBufferedCompiled is not static variable, it's bound to dbcontext and live with him in the same scope (members on NextormBenchmarks class). Nextorm is not yet supports static compiled queries as EF Core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, but that makes this a very questionable metric; I'm not familiar with Nextorm to know what
Compile()
does internally here, and whether it is optional/mandatory/recommended, but: by default I'd question anything namedCompile()
that occurs per connection/contextThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's better to name the function Prepare, since it build sql stmt, create DbCommand and so on. I'll think about it when start implementing static compiled queries (as in EF Core). Thank you, it is good remark!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, but: I think at that point, the entirety of that needs to go into the per-invoke code path. The question ultimately is: in a real world usage, what parts are going to be reused? It doesn't sound like any of this is ever going to be reused,l between real world calls, in which case: don't reuse it. It doesn't represent a meaningful scenario if you measure reusing something that can't usefully be reused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this does additional work, I would guess (I haven't measured) that it only actually becomes an optimization in the N+1 case for some non-trivial number of invokes - for example, for 10 rows it may be more efficient not to do this; it might be interesting to investigate (for your own purposes I mean, not really useful for Dapper); I had a moment, so I threw together:
which gives us:
from which we can observe that:
that's the thing that the static approach in EF attempts to resolve, i.e. so that the compilation overhead can be amortized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but in this version it is not supported. So let's return to this question after next version (should be in 2-3 weeks, I hope).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will be happy to join such benchmark
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very mush for the investigation and benchmarking this problem! Can you submit this code into my repo? I'll include it in future PR.
I also trying to measure cold start performance for EF and Dapper. There is PurgeQueryCache in Dapper, but I didn't find method to clear cache in EF. The code should look something like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "query cache" in dapper is not the same thing - in most reasonable scenarios, it should never be useful (let alone desirable) to call that API; it mostly just deals with how to handle
<LargeEntity>
andnew { id: int }
- at the type level - and those things will be identical for every similar usage, i.e. the entire point is that it doesn't change per call. It doesn't even exist in DapperAOT, because the entire point is that we emit the handling of<LargeEntity>
andnew { id: int }