C# 有效地消除.NET表达式树中的常见子表达式
我已经编写了一个DSL和一个编译器,可以从中生成一个.NET表达式树。 树中的所有表达式都没有副作用,并且表达式保证是“非语句”表达式(没有局部变量、循环、块等)。(编辑:树可能包括文字、属性访问、标准运算符和函数调用-这些操作可能在内部执行类似于记忆的奇特操作,但在外部没有副作用) 现在我想对它执行“公共子表达式消除”优化 例如,给定一棵对应于C#lambda的树:C# 有效地消除.NET表达式树中的常见子表达式,c#,.net,algorithm,optimization,expression-trees,C#,.net,Algorithm,Optimization,Expression Trees,我已经编写了一个DSL和一个编译器,可以从中生成一个.NET表达式树。 树中的所有表达式都没有副作用,并且表达式保证是“非语句”表达式(没有局部变量、循环、块等)。(编辑:树可能包括文字、属性访问、标准运算符和函数调用-这些操作可能在内部执行类似于记忆的奇特操作,但在外部没有副作用) 现在我想对它执行“公共子表达式消除”优化 例如,给定一棵对应于C#lambda的树: foo=>(foo.Bar*5+foo.Baz*2>7) ||(食物棒*5+食物棒*2 { var local1=foo.Bar
foo=>(foo.Bar*5+foo.Baz*2>7)
||(食物棒*5+食物棒*2<3)
||(foo.Bar*5+3==foo.Xyz)
…我想生成树等价物(忽略某些短路语义被忽略的事实):
foo=>
{
var local1=foo.Bar*5;
//注意,这个局部变量依赖于第一个局部变量。
var local2=local1+foo.Baz*2;
//请注意,没有生成不必要的局部变量。
返回local2>7 | | local2<3 | |(local1+3==foo.Xyz);
}
我对编写表达式访问者很熟悉,但这种优化的算法对我来说并不明显——我当然可以在树中找到“重复项”,但显然有一些技巧可以分析子树内和子树之间的依赖关系,从而高效、正确地消除子表达式
我在谷歌上寻找算法,但它们似乎很复杂,很难快速实现。此外,它们似乎非常“通用”,不一定考虑到我所拥有的树的简单性。免责声明:我从未解决过这样的问题,我只是抛出了一个似乎相当有效的想法: 树中的每个节点都有某种签名。散列应该可以,冲突可以处理。签名必须将所有Foo.Bar条目映射到相同的值 遍历树(O(n))构建内部节点的签名列表(忽略叶子),在表达式大小的组合键上排序,然后签名(O(n log n))。取列表中最小表达式的最常用项(O(n))并用局部变量替换该表达式。(此时请检查它们是否真正匹配,以防发生哈希冲突。B) 重复这个步骤,直到你一事无成。这不可能运行超过n/2次,因此将整个操作限定为O(n^2 log n)
表达式的SortedDictionary
。
(您可以在此处定义自己的任意比较函数——例如,您可以按字典顺序比较表达式的类型,如果它们比较相等,则可以逐个比较子表达式。)
(如果这个叶是它的第一个实例,那么现在也是为它发出代码的好时机,比如创建一个新变量;然后,您可以将发出的代码存储在字典中的
对象
值中。)现在,您知道了所有重复项是什么,它们发生在哪里,并为它们生成了代码。您正在做不必要的工作,常见子表达式消除是抖动优化器的工作。让我们以您的示例为例,看看生成的代码。我是这样写的:
static void Main(string[] args) {
var lambda = new Func<Foo, bool>(foo =>
(foo.Bar * 5 + foo.Baz * 2 > 7)
|| (foo.Bar * 5 + foo.Baz * 2 < 3)
|| (foo.Bar * 5 + 3 == foo.Xyz));
var obj = new Foo() { Bar = 1, Baz = 2, Xyz = 3 };
var result = lambda(obj);
Console.WriteLine(result);
}
}
class Foo {
public int Bar { get; internal set; }
public int Baz { get; internal set; }
public int Xyz { get; internal set; }
}
static void Main(字符串[]args){
var lambda=新函数(foo=>
(foo.Bar*5+foo.Baz*2>7)
||(食物棒*5+食物棒*2<3)
||(foo.Bar*5+3==foo.Xyz));
var obj=newfoo(){Bar=1,Baz=2,Xyz=3};
var结果=λ(obj);
控制台写入线(结果);
}
}
福班{
公共整型条{get;内部集;}
公共int Baz{get;内部集;}
公共int Xyz{get;内部集;}
}
x86抖动为lambda表达式生成了以下机器代码:
006526B8 push ebp ; prologue
006526B9 mov ebp,esp
006526BB push esi
006526BC mov esi,dword ptr [ecx+4] ; esi = foo.Bar
006526BF lea esi,[esi+esi*4] ; esi = 5 * foo.Bar
006526C2 mov edx,dword ptr [ecx+8] ; edx = foo.Baz
006526C5 add edx,edx ; edx = 2 * foo.Baz
006526C7 lea eax,[esi+edx] ; eax = 5 * foo.Bar + 2 * foo.Baz
006526CA cmp eax,7 ; > 7 test
006526CD jg 006526E7 ; > 7 then return true
006526CF add edx,esi ; HERE!!
006526D1 cmp edx,3 ; < 3 test
006526D4 jl 006526E7 ; < 3 then return true
006526D6 add esi,3 ; HERE!!
006526D9 mov eax,esi
006526DB cmp eax,dword ptr [ecx+0Ch] ; == foo.Xyz test
006526DE sete al ; convert to bool
006526E1 movzx eax,al
006526E4 pop esi ; epilogue
006526E5 pop ebp
006526E6 ret
006526E7 mov eax,1
006526EC pop esi
006526ED pop ebp
006526EE ret
006526B8推式ebp;开场白
006526B9 mov电子制动盘,esp
006526BB推式esi
006526BC电影esi,dword ptr[ecx+4];esi=foo.Bar
006526BF lea esi,[esi+esi*4];esi=5*foo.Bar
006526C2 mov edx,dword ptr[ecx+8];edx=foo.Baz
006526C5增加edx,edx;edx=2*foo.Baz
006526C7 lea eax,[esi+edx];eax=5*foo.Bar+2*foo.Baz
006526CA cmp eax,7;>7试验
006526CD jg 006526E7;>7然后返回true
006526CF添加edx、esi;在这里
006526D1 cmp edx,3;<3试验
006526D4 jl 006526E7;<3然后返回true
006526D6增加esi,3;在这里
006526D9 mov eax,esi
006526DB cmp eax,dword ptr[ecx+0Ch];==foo.Xyz测试
006526DE sete al;转换成布尔
006526E1 movzx eax,铝
006526E4 pop esi;后记
006526E5波普ebp
006526E6 ret
006526E7 mov eax,1
006526EC pop esi
006526ED pop ebp
006526EE ret
我在代码中用HERE标记了消除foo.Bar*5
子表达式的位置。值得注意的是,它没有消除foo.Bar*5+foo.Baz*2
子表达式,而是在地址006526CF处再次执行添加。
static void Main(string[] args) {
var lambda = new Func<Foo, bool>(foo =>
(foo.Bar * 5 + foo.Baz * 2 > 7)
|| (foo.Bar * 5 + foo.Baz * 2 < 3)
|| (foo.Bar * 5 + 3 == foo.Xyz));
var obj = new Foo() { Bar = 1, Baz = 2, Xyz = 3 };
var result = lambda(obj);
Console.WriteLine(result);
}
}
class Foo {
public int Bar { get; internal set; }
public int Baz { get; internal set; }
public int Xyz { get; internal set; }
}
006526B8 push ebp ; prologue
006526B9 mov ebp,esp
006526BB push esi
006526BC mov esi,dword ptr [ecx+4] ; esi = foo.Bar
006526BF lea esi,[esi+esi*4] ; esi = 5 * foo.Bar
006526C2 mov edx,dword ptr [ecx+8] ; edx = foo.Baz
006526C5 add edx,edx ; edx = 2 * foo.Baz
006526C7 lea eax,[esi+edx] ; eax = 5 * foo.Bar + 2 * foo.Baz
006526CA cmp eax,7 ; > 7 test
006526CD jg 006526E7 ; > 7 then return true
006526CF add edx,esi ; HERE!!
006526D1 cmp edx,3 ; < 3 test
006526D4 jl 006526E7 ; < 3 then return true
006526D6 add esi,3 ; HERE!!
006526D9 mov eax,esi
006526DB cmp eax,dword ptr [ecx+0Ch] ; == foo.Xyz test
006526DE sete al ; convert to bool
006526E1 movzx eax,al
006526E4 pop esi ; epilogue
006526E5 pop ebp
006526E6 ret
006526E7 mov eax,1
006526EC pop esi
006526ED pop ebp
006526EE ret
Binary OR (27,1)
lhs:
Binary OR (19,1)
lhs:
Binary GREATER (9,1)
lhs:
Binary ADD (7,2)
lhs:
Binary MULTIPLY (3,2)
lhs:
Id 'Bar' (1,1)
rhs:
Number 5 (2,1)
rhs:
Binary MULTIPLY (6,1)
lhs:
Id 'Baz' (4,1)
rhs:
Number 2 (5,1)
rhs:
Number 7 (8,1)
rhs:
Binary LESS (18,1)
lhs:
ref to Binary ADD (7,2)
rhs:
Number 3 (17,2)
rhs:
Binary EQUALS (26,1)
lhs:
Binary ADD (24,1)
lhs:
ref to Binary MULTIPLY (3,2)
rhs:
ref to Number 3 (17,2)
rhs:
Id 'Xyz' (25,1)
t3 = (Bar) * (5);
t7 = (t3) + ((Baz) * (2));
return (((t7) > (7)) || ((t7) < (3))) || (((t3) + (3)) == (Xyz));