【发布时间】:2019-07-19 22:43:48
【问题描述】:
我有代码可以在运行时使用表达式树为任何类型生成排序函数。下面的示例按 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();
}
【问题讨论】:
-
嗨,你能举个例子来说明我们正在比较什么吗?即“手写”的东西 - 是手写
IComparer<T>实现还是OrderBy/ThenBy链? -
嗨,Ivan,我的意思是像上面的 SortOneIntOneStrHC 这样的表单函数,它被传递给 MyComparer ctor 以创建比较器
-
使用 CompileToMethod,正如您所指出的问题所建议的那样,使表达式树生成方法的性能非常接近手工编码的 C#。例如在我的一项基准测试中,生成的方法需要 22.2 毫秒,手动编码的 C# 需要 20.6 毫秒。谢谢
-
使用我的函数的 CompileToMethod 版本,我现在得到 System.MethodAccessExeceptions,任何关于如何修复的想法都将不胜感激。
标签: c# sorting expression-trees