Compiler optimization of repeated accessor calls

喜欢而已 提交于 2020-01-06 06:24:31

问题


I've found recently that for some types of financial calculations that the following pattern is much easier to follow and test especially in situations where we may need to get numbers from various stages of the computation.

public class nonsensical_calculator
{ 

   ...

    double _rate;
    int _term;
    int _days;

    double monthlyRate { get { return _rate / 12; }}

    public double days { get { return (1 - i); }}
    double ar   { get { return (1+ days) /(monthlyRate  * days)
    double bleh { get { return Math.Pow(ar - days, _term)
    public double raar { get { return bleh * ar/2 * ar / days; }}
    ....
}

Obviously this often results in multiple calls to the same accessor within a given formula. I was curious as to whether or not the compiler is smart enough to optimize away these repeated calls with no intervening change in state, or whether this style is causing a decent performance hit.

Further reading suggestions are always appreciated


回答1:


From what I know, the C# compiler doesn't optimize this, because it can't be certain of side-effects (e.g. what if you have accessCount++ in the getter?) Take a look here at an excellent answer by Eric Lippert

From that answer:

The C# compiler never ever does this sort of optimization; as noted, doing so would require that the compiler peer into the code being called and verify that the result it computes does not change over the lifetime of the callee's code. The C# compiler does not do so.

The JIT compiler might. No reason why it couldn't. It has all the code sitting right there. It is completely free to inline the property getter, and if the jitter determines that the inlined property getter returns a value that can be cached in a register and re-used, then it is free to do so. (If you don't want it to do so because the value could be modified on another thread then you already have a race condition bug; fix the bug before you worry about performance.)

Just a note, seeing as Eric's on the C# compiler team, I trust his answer :)




回答2:


A few random thoughts.

First, as others have noted, the C# compiler does not do this sort of optimization, though the jitter is free to do so.

Second, the best way to answer a performance question is to try it and see. The Stopwatch class is your friend. Try it a billion times both ways and see which one is faster; then you'll know.

Third, of course it makes no sense to spend time optimizing something that is already fast enough. Before you spend a lot of time benchmarking, spend some time profiling and looking for hot spots. This is unlikely to be one.

And fourth, another answer suggested storing intermediate results in a local variable. Note that doing so can in some situations make things considerably faster, and in others, can make it slower. Sometimes it is faster to recompute a result unnecessarily than to store it and look it up again when you need it.

How can that be? Chip architectures with a small number of registers -- I'm looking at you, x86 -- require the jitter to be very judicious about which locals get to be in registers and which get to be stack accesses. Encouraging the jitter to put something that is used infrequently in one register sometimes means forcing something else out of that register, something that would provide more benefit from being in a register than your infrequently used value.

In short: do not try to second-guess the jitter from your comfortable armchair; the behaviour of real-world code can be deeply counterintuitive. Make performance decisions based on realistic empirical measurements.




回答3:


Right, the C# compiler doesn't make optimizations like this. But the JIT compiler certainly does. All the getters you posted are small enough to get inlined, resulting in a direct access to the field.

An example:

static void Main(string[] args) {
  var calc = new nonsensical_calculator(42);
  double rate = calc.monthlyRate;
  Console.WriteLine(rate);
}

Generates:

00000000  push        ebp                          ; setup stack frame
00000001  mov         ebp,esp 
00000003  sub         esp,8 
00000006  mov         ecx,349DFCh                  ; eax = new nonsensical_calculator
0000000b  call        FFC50AD4 
00000010  fld         dword ptr ds:[006E1590h]     ; st0 = 42
00000016  fstp        qword ptr [eax+4]            ; _rate = st0
00000019  fld         qword ptr [eax+4]            ; st0 = _rate
0000001c  fdiv        dword ptr ds:[006E1598h]     ; st0 = st0 / 12
00000022  fstp        qword ptr [ebp-8]            ; rate = st0
      Console.WriteLine(rate);
// etc..

Note how both the constructor call and the property getter have disappeared, they are inlined into Main(). The code is directly accessing the _rate field. Even the calc variable is gone, the reference is held in the eax register.

The instruction at address 19 shows that more work could be done on the optimizer. Time permitting.




回答4:


To put a slightly different spin on this, consider that properties are really just wrappers around methods once the code is compiled to IL. So if, instead of this:

public class nonsensical_calculator
{
    double bleh
    {
        get { return Math.Pow(ar - days, _term); }
    }
    // etc.
}

You had this:

public class nonsensical_calculator
{
    double GetBleh()
    {
        return Math.Pow(ar - days, _term);
    }
}

Would you expect the compiler to optimize out the method call for you?

I'm not an expert on the jitter but I doubt that even the jitter will "cache" this; it would have to track all sorts of state and invalidate the entry when any of the dependent fields change, and as awesome as the .NET jitter is, I just don't think it's that clever. It may inline the method, but that usually won't make a huge difference performance-wise.

Bottom line, don't rely on the compiler or jitter to make these optimizations for you. Also, you might consider following the common design guideline of not putting expensive computations in property getters, because it appears to the caller to be cheap, even though it might not be.

If you need performance, then precompute these values whenever the dependent fields change. Or, better yet, profile the code using a tool like EQATEC (free) or ANTS and see if where the performance cost really is. Optimizing without profiling is like shooting with a blindfold on.



来源:https://stackoverflow.com/questions/2497113/compiler-optimization-of-repeated-accessor-calls

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!