Efficiently eliminate common sub-expressions in .NET Expression Tree

前端 未结 5 1480
無奈伤痛
無奈伤痛 2020-12-29 06:42

I\'ve written a DSL and a compiler that generates a .NET expression tree from it. All expressions within the tree are side-effect-free and the expression is guaranteed to b

5条回答
  •  生来不讨喜
    2020-12-29 06:53

    You are doing unnecessary work, common sub-expression elimination is the job of the jitter optimizer. Let's take your example and look at the generated code. I wrote it like this:

        static void Main(string[] args) {
            var lambda = new Func(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; }
    }
    

    The x86 jitter generated this machine code for the lambda expression:

    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   
    

    I marked the places in the code where the foo.Bar * 5 sub-expression was eliminated with HERE. Notable is how it did not eliminate the foo.Bar * 5 + foo.Baz * 2 sub-expression, the addition was performed again at address 006526CF. There is a good reason for that, the x86 jitter doesn't have enough registers available to store the intermediary result. If you look at the machine code generated by the x64 jitter then you do see it eliminated, the r9 register stores it.

    This ought to give enough reasons to reconsider your intend. You are doing work that doesn't need to be done. And not only that, you are liable to generate worse code than the jitter will generate since you don't have the luxury to estimate the CPU register budget.

    Don't do this.

提交回复
热议问题