Skip to content
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

Performance improvement #177

Merged
merged 13 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions benchmark/NativeLinqComparisonBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Order;
using Gridify;
using Gridify.Tests;

namespace Benchmarks;

[MemoryDiagnoser]
[RPlotExporter]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class NativeLinqComparisonBenchmark
{
private static readonly Consumer Consumer = new();
private TestClass[] _data;
private GridifyMapper<TestClass> _gm;

private IQueryable<TestClass> Ds => _data.AsQueryable();

[GlobalSetup]
public void Setup()
{
_data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray();
_gm = new GridifyMapper<TestClass>(true);
}


[Benchmark(Baseline = true)]
public void Native_LINQ()
{
Ds.Where(q => q.Name.Contains('a')).Consume(Consumer);
Ds.Where(q => q.Id > 5).Consume(Consumer);
Ds.Where(q => q.Name == "Ali").Consume(Consumer);
}

[Benchmark]
public void Gridify()
{
Ds.ApplyFiltering("Name=*a", _gm).Consume(Consumer);
Ds.ApplyFiltering("Id>5", _gm).Consume(Consumer);
Ds.ApplyFiltering("Name=Ali", _gm).Consume(Consumer);
}

[Benchmark]
public void Gridify_WithoutMapper()
{
Ds.ApplyFiltering("Name=*a").Consume(Consumer);
Ds.ApplyFiltering("Id>5").Consume(Consumer);
Ds.ApplyFiltering("Name=Ali").Consume(Consumer);
}
}
3 changes: 2 additions & 1 deletion benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public class Program
{
private static void Main()
{
BenchmarkRunner.Run<LibraryComparisionFilteringBenchmark>();
// BenchmarkRunner.Run<LibraryComparisionFilteringBenchmark>();
BenchmarkRunner.Run<NativeLinqComparisionBenchmark>();
// BenchmarkRunner.Run<LibraryComparisionFilteringBenchmark2>();
// BenchmarkRunner.Run<GridifyMapperUsages>();
// BenchmarkRunner.Run<QueryBuilderBuildBenchmark>();
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/configs/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const version: string = '2.15.0-preview1'
export const version: string = '2.15.0-preview2'
4 changes: 2 additions & 2 deletions src/Gridify.Elasticsearch/ElasticsearchQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class ElasticsearchQueryBuilder<T>(IGridifyMapper<T> mapper) : BaseQuer
private readonly IGridifyMapper<T> _mapper = mapper;

protected override Query BuildNestedQuery(
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, SyntaxNode op)
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, ISyntaxNode op)
{
throw new NotSupportedException();
}
Expand All @@ -40,7 +40,7 @@ protected override object BuildQueryAccordingToValueType(
Expression body,
ParameterExpression parameter,
object? value,
SyntaxNode op,
ISyntaxNode op,
ValueExpressionSyntax valueExpression)
{
if (valueExpression.IsCaseInsensitive)
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify.Elasticsearch/Gridify.Elasticsearch.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Gridify.Elasticsearch</PackageId>
<Version>2.15.0-preview1</Version>
<Version>2.15.0-preview2</Version>
<Authors>Alireza Sabouri; Dzmitry Koush</Authors>
<PackageDescription>Gridify (Elasticsearch), Easy way to apply Filtering, Sorting, and Pagination using text-based data.</PackageDescription>
<RepositoryUrl>https://github.com/alirezanet/Gridify</RepositoryUrl>
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify.EntityFramework/Gridify.EntityFramework.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Gridify.EntityFramework</PackageId>
<Version>2.15.0-preview1</Version>
<Version>2.15.0-preview2</Version>
<Authors>Alireza Sabouri</Authors>
<Company>TuxTeam</Company>
<PackageDescription>Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data.</PackageDescription>
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify/Gridify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<PackageId>Gridify</PackageId>
<Version>2.15.0-preview1</Version>
<Version>2.15.0-preview2</Version>
<Authors>Alireza Sabouri</Authors>
<Company>TuxTeam</Company>
<PackageDescription>Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data.</PackageDescription>
Expand Down
32 changes: 16 additions & 16 deletions src/Gridify/GridifyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,28 +178,28 @@ internal static IGridifyMapper<T> FixMapper<T>(this IGridifyMapper<T>? mapper, S

mapper = new GridifyMapper<T>();

var fields = syntaxTree.Root.Descendants()
.Where(q => q.Kind == SyntaxKind.FieldExpression)
.Cast<FieldExpressionSyntax>()
.Distinct(new FieldExpressionComparer());
var root = syntaxTree.Root;
var fields = root.Descendants()
.OfType<FieldExpressionSyntax>()
.Distinct(new FieldExpressionComparer());

foreach (var field in fields)
try
{
mapper.AddMap(field.FieldToken.Text);
}
catch (Exception)
{
if (!mapper.Configuration.IgnoreNotMappedFields)
throw new GridifyMapperException($"Property '{field.FieldToken.Text}' not found.");
}
try
{
foreach (var field in fields) mapper.AddMap(field.FieldToken.Text);
}
catch (Exception)
{
if (!mapper.Configuration.IgnoreNotMappedFields)
throw;
}

return mapper;
}

private static IEnumerable<SyntaxNode> Descendants(this SyntaxNode root)

private static IEnumerable<ISyntaxNode> Descendants(this ISyntaxNode root)
{
var nodes = new Stack<SyntaxNode>(new[] { root });
var nodes = new Stack<ISyntaxNode>(new[] { root });
while (nodes.Any())
{
var node = nodes.Pop();
Expand Down
26 changes: 19 additions & 7 deletions src/Gridify/GridifyMapper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Gridify.Syntax;

namespace Gridify;

Expand Down Expand Up @@ -47,7 +47,7 @@ public IGridifyMapper<T> AddMap(string from, Func<string, object>? convertor = n
Expression<Func<T, object>> to;
try
{
to = CreateExpression(from);
to = CreateExpression(from); ;
}
catch (Exception)
{
Expand Down Expand Up @@ -81,7 +81,7 @@ private void GenerateMappingsRecursive(Type type, string prefix, ushort maxNesti
var fullName = string.IsNullOrEmpty(prefix) ? propertyName : $"{prefix}.{propertyName}";

// Skip classes if nestingLevel is exceeded
if (item.PropertyType.IsClass && item.PropertyType != typeof(string))
if (item.PropertyType.IsClass && item.PropertyType != typeof(string) && !item.PropertyType.IsSimpleTypeCollection(out _))
{
if (currentDepth >= maxNestingDepth)
{
Expand Down Expand Up @@ -216,10 +216,22 @@ internal static Expression<Func<T, object>> CreateExpression(string from)
var parameter = Expression.Parameter(typeof(T), "__" + typeof(T).Name);
// Param_x.Name, Param_x.yyy.zz.xx
var mapProperty = from.Split('.').Aggregate<string, Expression>(parameter, Expression.Property);
// (object)Param_x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(object));
// Param_x => (object)Param_x.Name
return Expression.Lambda<Func<T, object>>(convertedExpression, parameter);

if (!mapProperty.Type.IsSimpleTypeCollection(out var genericType))
{
// (object)Param_x.Name
var convertedExpression = Expression.Convert(mapProperty, typeof(object));
// Param_x => (object)Param_x.Name
return Expression.Lambda<Func<T, object>>(convertedExpression, parameter);
}

var selectMethod = genericType!.GetSimpleTypeSelectMethod();
var predicateParameter = Expression.Parameter(genericType!);
var predicate = Expression.Lambda(predicateParameter, predicateParameter);
// Param_x.Name.Select(fc => fc)
var body = Expression.Call(selectMethod, mapProperty, predicate);
return Expression.Lambda<Func<T, object>>(body, parameter);
}


}
6 changes: 3 additions & 3 deletions src/Gridify/QueryBuilders/BaseQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal TQuery Build(ExpressionSyntax expression)
}

protected abstract TQuery? BuildNestedQuery(
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, SyntaxNode op);
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, ISyntaxNode op);

protected abstract TQuery BuildAlwaysTrueQuery();

Expand All @@ -31,7 +31,7 @@ internal TQuery Build(ExpressionSyntax expression)
Expression body,
ParameterExpression parameter,
object? value,
SyntaxNode op,
ISyntaxNode op,
ValueExpressionSyntax valueExpression);

protected abstract TQuery CombineWithAndOperator(TQuery left, TQuery right);
Expand Down Expand Up @@ -157,7 +157,7 @@ private static object AddIndexerNullCheck(IGMap<T> gMap, object query)
Expression body,
ParameterExpression parameter,
ValueExpressionSyntax valueExpression,
SyntaxNode op,
ISyntaxNode op,
Func<string, object>? convertor)
{
// Remove the boxing for value types
Expand Down
59 changes: 47 additions & 12 deletions src/Gridify/QueryBuilders/LinqQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Gridify.QueryBuilders;
internal class LinqQueryBuilder<T>(IGridifyMapper<T> mapper) : BaseQueryBuilder<Expression<Func<T, bool>>, T>(mapper)
{
protected override Expression<Func<T, bool>>? BuildNestedQuery(
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, SyntaxNode op)
Expression body, IGMap<T> gMap, ValueExpressionSyntax value, ISyntaxNode op)
{
while (true)
switch (body)
Expand All @@ -27,7 +27,7 @@ internal class LinqQueryBuilder<T>(IGridifyMapper<T> mapper) : BaseQueryBuilder<

if (conditionExp is not LambdaExpression lambdaExp) return null;

return ParseMethodCallExpression(selectExp, lambdaExp) as Expression<Func<T, bool>>;
return ParseMethodCallExpression(selectExp, lambdaExp, op) as Expression<Func<T, bool>>;
}
case ConditionalExpression cExp:
{
Expand Down Expand Up @@ -105,7 +105,7 @@ protected override Expression<Func<T, bool>> BuildAlwaysFalseQuery(ParameterExpr
Expression body,
ParameterExpression parameter,
object? value,
SyntaxNode op,
ISyntaxNode op,
ValueExpressionSyntax valueExpression)
{
Expression be;
Expand Down Expand Up @@ -175,10 +175,10 @@ protected override Expression<Func<T, bool>> BuildAlwaysFalseQuery(ParameterExpr
be = GetLessThanOrEqualExpression(body, valueExpression, value);
break;
case SyntaxKind.Like:
be = Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value));
be = Expression.Call(body, GetStringContainsMethod(), GetValueExpression(body.Type, value));
break;
case SyntaxKind.NotLike:
be = Expression.Not(Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value)));
be = Expression.Not(Expression.Call(body, GetStringContainsMethod(), GetValueExpression(body.Type, value)));
break;
case SyntaxKind.StartsWith:
if (body.Type != typeof(string))
Expand Down Expand Up @@ -229,7 +229,7 @@ protected override Expression<Func<T, bool>> BuildAlwaysFalseQuery(ParameterExpr

break;
case SyntaxKind.CustomOperator:
var token = op as SyntaxToken;
var token = (SyntaxToken)op;
var customOperator = GridifyGlobalConfiguration.CustomOperators.Operators.First(q => q.GetOperator() == token!.Text);
var customExp = customOperator.OperatorHandler();

Expand Down Expand Up @@ -257,7 +257,7 @@ protected override Expression<Func<T, bool>> CombineWithOrOperator(Expression<Fu
return left.Or(right);
}

private Expression<Func<T, bool>>? GenerateNestedExpression(Expression body, IGMap<T> gMap, ValueExpressionSyntax value, SyntaxNode op)
private Expression<Func<T, bool>>? GenerateNestedExpression(Expression body, IGMap<T> gMap, ValueExpressionSyntax value, ISyntaxNode op)
{
while (true)
switch (body)
Expand All @@ -269,7 +269,7 @@ protected override Expression<Func<T, bool>> CombineWithOrOperator(Expression<Fu

if (conditionExp is not LambdaExpression lambdaExp) return null;

return ParseMethodCallExpression(selectExp, lambdaExp) as Expression<Func<T, bool>>;
return ParseMethodCallExpression(selectExp, lambdaExp, op) as Expression<Func<T, bool>>;
}
case ConditionalExpression cExp:
{
Expand All @@ -296,18 +296,27 @@ protected override Expression<Func<T, bool>> CombineWithOrOperator(Expression<Fu
}
}

private LambdaExpression ParseMethodCallExpression(MethodCallExpression exp, LambdaExpression predicate)
private static LambdaExpression ParseMethodCallExpression(MethodCallExpression exp, LambdaExpression predicate, ISyntaxNode op)
{
switch (exp.Arguments.First())
{
case MemberExpression member:
{
if (op.Kind is SyntaxKind.Equal or SyntaxKind.NotEqual &&
member.Type.IsSimpleTypeCollection(out _) &&
predicate.Body is BinaryExpression binaryExpression)
{
return GetContainsExpression(member, binaryExpression, op);
}

return GetAnyExpression(member, predicate);
}
case MethodCallExpression { Method.Name: "SelectMany" } subExp
when subExp.Arguments.Last()
is LambdaExpression { Body: MemberExpression lambdaMember }:
{
var newPredicate = GetAnyExpression(lambdaMember, predicate);
return ParseMethodCallExpression(subExp, newPredicate);
return ParseMethodCallExpression(subExp, newPredicate, op);
}
case MethodCallExpression { Method.Name: "Select" } subExp
when subExp.Arguments.Last() is LambdaExpression
Expand All @@ -317,13 +326,34 @@ when subExp.Arguments.Last() is LambdaExpression
{
var newExp = new ReplaceExpressionVisitor(predicate.Parameters[0], lambdaMember).Visit(predicate.Body);
var newPredicate = GetExpressionWithNullCheck(lambdaMember, lambda.Parameters[0], newExp);
return ParseMethodCallExpression(subExp, newPredicate);
return ParseMethodCallExpression(subExp, newPredicate, op);
}
default:
throw new InvalidOperationException();
}
}

private static LambdaExpression GetContainsExpression(MemberExpression member, BinaryExpression binaryExpression, ISyntaxNode op)
{
var param = GetParameterExpression(member);
var prop = GetPropertyOrField(member, param);

var tp = prop.Type.IsGenericType
? prop.Type.GenericTypeArguments.First() // list
: prop.Type.GetElementType(); // array

if (tp == null) throw new GridifyFilteringException($"Can not detect the '{member.Member.Name}' property type.");
var containsMethod = GetContainsMethod(tp);
Expression containsExp = Expression.Call(containsMethod, prop, binaryExpression.Right);
if (op.Kind == SyntaxKind.NotEqual)
{
containsExp = Expression.Not(containsExp);
}
return GetExpressionWithNullCheck(prop, param, containsExp);
}



private static ParameterExpression GetParameterExpression(MemberExpression member)
{
return member.Expression switch
Expand Down Expand Up @@ -451,11 +481,16 @@ private static MethodInfo GetStartWithMethod()
return typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!;
}

private static MethodInfo GetContainsMethod()
private static MethodInfo GetStringContainsMethod()
{
return typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
}

private static MethodInfo GetContainsMethod(Type tp)
{
return typeof(Enumerable).GetMethods().First(x => x.Name == "Contains").MakeGenericMethod(tp);
}

private static MethodInfo GetIsNullOrEmptyMethod()
{
return typeof(string).GetMethod("IsNullOrEmpty", new[] { typeof(string) })!;
Expand Down
Loading