3

I was trying to see how Roslyn optimizes the following snippet:

code

public int F(int n) {
    ++n;       
    ++n;       
    ++n;       
    ++n;       

    return n;
}              

asm

C.F(Int32)
    L0000: inc edx
    L0002: inc edx
    L0004: inc edx
    L0006: inc edx
    L0008: mov eax, edx
    L000a: ret

Question

why doesn't Roslyn optimize it like an ahead-of-time C compiler like MSVC? 4 x INC is slower (4 cycle latency vs. 1 even assuming mov-elimination, and 4 more uops than necessary for throughput; https://agner.org/optimize/).

C "equivalent" of it:

int
f(void *dummy_this, int n) {
        ++n;        
        ++n;        
        ++n;        
        ++n;        

        return n;
}

asm from MSVC, or GCC with __attribute__((ms_abi)) to use the same Windows x64 calling convention as the C# asm: https://godbolt.org/z/sK6h7KKcn

f:
        lea     eax, [rdx+4]
        ret
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/230322/discussion-on-question-by-hrant-is-there-a-reason-why-roslyn-does-not-optimize-m). – Samuel Liew Mar 24 '21 at 13:39

1 Answers1

-5

The compiler does optimize. n is a parameter though, so it can't be modified. The JIT compiler must modify a copy of the parameter's value.

If the value is assigned to a variable before incrementing, the Roslyn compiler will eliminate the increments. From this Sharplab.io snippet, this C# code :

public int F(int i) {
    var n=i;
    ++n;       
    ++n;       
    ++n;       
    ++n;       

    return n;
} 

Will be translated to

public int F(int i)
{
    return i + 1 + 1 + 1 + 1;
}

And eventually compiled to this assembly code:

C.F(Int32)
    L0000: lea eax, [edx+4]
    L0003: ret
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • 5
    "`n` is a parameter though, so it can't be modified" - but `n` is `Int32`, a value-type. It isn't being passed by-`ref` - and both parameters and locals in the CLR's abstract stack behave the same, so I don't see why you're saying "it can't be modified". – Dai Mar 23 '21 at 16:15
  • 4
    @Dai is right. "n is a parameter though, so it can't be modified" doesn't make sense and is also flatly invalidated by the OP's disassembly (the register containing the parameter is clearly incremented, just not very efficiently). Assigning it to another variable shouldn't make a difference. – Peter - Reinstate Monica Mar 23 '21 at 16:17
  • @Peter-ReinstateMonica I think you are correct. That's why I called this version a "workaround". However it's good to know that there is at least some way of doing it. –  Mar 23 '21 at 16:22
  • @Hrant: as discussed in comments on the question (now unfortunately moved to chat), this answer doesn't explain it (so you should un-accept it). That's not how C# or the Windows x64 calling convention works. (If `int i` was taken by ref (which it isn't), its value wouldn't be passed/returned in EDX; it would be by a pointer. So `inc edx` is purely private the the function, and worse than mov+add, or LEA.) – Peter Cordes Mar 25 '21 at 18:18
  • @PeterCordes you are absolutely correct, but I'd say that this answer is better than nothing. And I also don't understand why was it moved to chat when there was extremely valuable information to read. –  Mar 25 '21 at 19:48
  • @Hrant: There is a useful workaround here, but that's all. It shouldn't be the *accepted* answer because it's extremely misleading (by claiming that there's a reason for the missed optimization, and even seems to be making incorrect claims about C# arg semantics.) You could post your own answer with the workaround, but pointing out that there's no good reason for it, or to have expected this in the first place. (Feel free to copy or quote any of what I wrote in comments, e.g. about calling conventions.) – Peter Cordes Mar 25 '21 at 20:34
  • @PeterCordes I think it'll be a bit unfair, because you gave us the insight and proved us that it is in fact a weakness of JIT compiler. So yeah you can write the answer and I'll accept it. –  Mar 25 '21 at 20:39
  • I don't feel like a minimal answer repeating the fairly well-known fact that JIT compilers generally aren't as good as ahead-of-time, because they have to compile fast. And I don't want to spend the time copy/pasting a GCC or MSVC example from https://godbolt.org/ like I did when editing your question. If you do, go ahead. Otherwise may there's a good canonical Q&A about JITs often having more missed optimizations than AoT compilers. – Peter Cordes Mar 25 '21 at 20:43