我有代码可以在运行时使用表达式树生成任何类型的排序函数。下面的示例按public int或string属性排序,但可以轻松扩展为包含其他类型的属性。生成的排序函数比手工编写的等效函数慢大约4倍。使用Benchmarkdotnet比较了执行时间与手动编码版本。如何更改生成代码以生成更快的排序函数?
public class SortBy
{
public bool Ascending { get; set; }
public string PropName {get; set;}
}
public static class SortFuncCompiler
{
private static readonly MethodInfo _strCompareTo = typeof(string).GetMethod("CompareTo", new[] {typeof(string)});
private static readonly MethodInfo _intCompareTo = typeof(int).GetMethod("CompareTo", new[] {typeof(int)});
public static Func<T,T,int> MakeSortFunc<T>(IList<SortBy> sortDescriptors)
{
ParameterExpression param1Expr = Expression.Parameter(typeof(T));
ParameterExpression param2Expr = Expression.Parameter(typeof(T));
BlockExpression exprSd = MakeCompositeCompare(param1Expr, param2Expr, sortDescriptors);
Expression<Func<T,T,int>> lambda = Expression.Lambda<Func<T,T,int>>(exprSd, param1Expr, param2Expr);
return lambda.Compile();
}
private static BlockExpression MakePropertyCompareBlock(
SortBy sortDescriptor,
ParameterExpression rm1,
ParameterExpression rm2,
LabelTarget labelReturn,
ParameterExpression result)
{
try
{
MemberExpression propA = Expression.Property(rm1, sortDescriptor.PropName);
MemberExpression propB = Expression.Property(rm2, sortDescriptor.PropName);
var (prop1, prop2) = sortDescriptor.Ascending ? (propA, propB) : (propB, propA);
Expression compareExpr;
if(prop1.Type == typeof(string))
{
compareExpr = Expression.Call(prop1, _strCompareTo, prop2);
}
else if(prop1.Type == typeof(int))
{
compareExpr = Expression.Call(prop1, _intCompareTo, prop2);
}
else
{
throw new ApplicationException($"unsupported property type: {prop1.Type}");
}
IEnumerable<ParameterExpression> variables = new[] {result};
IEnumerable<Expression> expressions = new Expression[]
{
Expression.Assign(result, compareExpr),
Expression.IfThen(
Expression.NotEqual(Expression.Constant(0), result),
Expression.Goto(labelReturn, result))
};
return Expression.Block(variables, expressions);
}
catch
{
throw new ApplicationException($"unknown property: {sortDescriptor.PropName}");
}
}
private static BlockExpression MakeCompositeCompare(ParameterExpression param1Expr, ParameterExpression param2Expr, IEnumerable<SortBy> sortBys )
{
ParameterExpression result = Expression.Variable(typeof(int), "result");
LabelTarget labelReturn = Expression.Label(typeof(int));
LabelExpression labelExpression = Expression.Label(labelReturn, result);
IEnumerable<Expression> compareBlocks = sortBys.Select(propName => MakePropertyCompareBlock(propName, param1Expr, param2Expr, labelReturn, result));
return Expression.Block(new[] {result}, compareBlocks.Append(labelExpression));
}
}
如何使用生成的排序功能
public class MyComparer<T> : IComparer<T>
{
private Func<T, T, int> _sortFunc;
public MyComparer(Func<T, T, int> sortFunc)
{
_sortFunc = sortFunc;
}
public int Compare(T x, T y) => _sortFunc(x, y);
}
//the expression-tree generated sorting function should be of form
static int SortOneIntOneStrHC(MyClass aa, MyClass bb)
{
int s1 = aa.IntProp1.CompareTo(bb.IntProp1);
if (s1 != 0) return s1;
// aa and bb flipped, as this comparison is descending
return bb.StrProp1.CompareTo(aa.StrProp1);
}
public class MyClass
{
public int IntProp1 { get; set; }
public int IntProp2 { get; set; }
public string StrProp1 { get; set; }
public string StrProp2 { get; set; }
}
void Main()
{
var xs = new List<MyClass>
{
new MyClass{IntProp1 = 99, IntProp2 = 88, StrProp1 = "aa", StrProp2 ="bb"},
new MyClass{IntProp1 = 11, IntProp2 = 22, StrProp1 = "xx", StrProp2 ="yy"},
new MyClass{IntProp1 = 11, IntProp2 = 22, StrProp1 = "pp", StrProp2 ="qq"},
};
var sortBys = new List<SortBy>
{
new SortBy{PropName = "IntProp2", Ascending = true},
new SortBy{PropName = "StrProp1", Ascending = false}
};
Func<MyClass, MyClass, int> sortMyClass = SortFuncCompiler.MakeSortFunc<MyClass>(sortBys);
var ys = xs.OrderBy(x => x, new MyComparer<MyClass>(sortMyClass));
ys.Dump();
}
答案 0 :(得分:1)
native
方法。稍后,您可以定义所做的更改是否使您的代码在性能方面更好.Compile()
很繁重。您应该为特定情况Func<T,T,int>
创建一次已编译的委托并将其缓存。答案 1 :(得分:0)
我编写了基准测试,结果本机与表达式非常接近:
| Method | N | Mean | Error | StdDev | Median |
|------- |------ |-----------:|-----------:|-----------:|-----------:|
| Linq | 1000 | 196.6 us | 3.431 us | 3.209 us | 197.5 us |
| Native | 1000 | 215.8 us | 8.043 us | 21.881 us | 208.0 us |
| Linq | 10000 | 3,094.8 us | 108.795 us | 306.857 us | 2,991.8 us |
| Native | 10000 | 3,078.2 us | 104.824 us | 107.647 us | 3,056.5 us |
也许您在基准代码本身中存在一些错误。这是我用的:
public class Benchmark
{
private MyClass[] data;
private MyComparer<MyClass> linqComparer;
private MyComparer<MyClass> nativeComparer;
[Params(1000, 10000)]
public int N;
[GlobalSetup]
public void Setup()
{
var random = new Random();
data = new MyClass[N];
for (int i = 0; i < N; ++i)
{
data[i] = MyClass.Generate(random, N*10, 25);
}
// Compile order methods
List<SortBy> sortBys = new List<SortBy>
{
new SortBy{PropName = "IntProp2", Ascending = true},
new SortBy{PropName = "StrProp1", Ascending = false}
};
Func<MyClass, MyClass, int> sortMyClass = SortFuncCompiler.MakeSortFunc<MyClass>(sortBys);
linqComparer = new MyComparer<MyClass>(sortMyClass);
nativeComparer = new MyComparer<MyClass>(Sorters.SortOneIntOneStrHC);
}
[Benchmark]
public MyClass[] Linq()
{
return data.OrderBy(x => x, linqComparer).ToArray();
}
[Benchmark]
public MyClass[] Native()
{
return data.OrderBy(x => x, nativeComparer).ToArray();
}
}
public class Program
{
public static void Main()
{
var summary = BenchmarkRunner.Run<Benchmark>();
}
}
这里最重要的是使用设置方法而不是基准测试来编译分类器。