Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# and JIT optimizers do not eliminate local variables #6542

Closed
breyed opened this issue Nov 3, 2015 · 8 comments
Closed

C# and JIT optimizers do not eliminate local variables #6542

breyed opened this issue Nov 3, 2015 · 8 comments
Labels
Area-Compilers Resolution-External The behavior lies outside the functionality covered by this repository

Comments

@breyed
Copy link

breyed commented Nov 3, 2015

Commenting out the first two lines of this for loop and uncommenting the third results in a 42% speedup:

int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
    var isMultipleOf16 = i % 16 == 0;
    count += isMultipleOf16 ? 1 : 0;
    //count += i % 16 == 0 ? 1 : 0;
}

Behind the timing is vastly different assembly code: 13 vs. 7 instructions in the loop. The platform is Windows 7 running .NET 4.0 x64. Code optimization is enabled, and the test app was run outside VS2010.

Repro project
StackOverflow question on this topic

@agocke
Copy link
Member

agocke commented Nov 4, 2015

VS2010? Roslyn is >= VS2015. Please confirm you can still repro and please post the IL that you think is incorrectly generated.

@breyed
Copy link
Author

breyed commented Nov 4, 2015

I reported this same issue on Microsoft Connected back in the day. It was eventually deleted without ever being marked as resolved, so I took that as a good sign that the issue still exists. I can double check though.

@breyed
Copy link
Author

breyed commented Nov 5, 2015

Retested with VS2015. Same behavior as on VS2010. The relevant IL follows.

Single line:

// count += i % 16 == 0 ? 1 : 0;
.locals init (
    [0] class [System]System.Diagnostics.Stopwatch stopwatch,
    [1] int32 count,
    [2] uint32 i
)
...
IL_0012: ldloc.1
IL_0013: ldloc.2
IL_0014: ldc.i4.s 16
IL_0016: rem.un
IL_0017: brfalse.s IL_001c

Multiple lines:

// var isMultipleOf16 = i % 16 == 0;
// count += isMultipleOf16 ? 1 : 0;
.locals init (
    [0] class [System]System.Diagnostics.Stopwatch stopwatch,
    [1] int32 count,
    [2] uint32 i,
    [3] bool isMultipleOf16
)
...
IL_0012: ldloc.2
IL_0013: ldc.i4.s 16
IL_0015: rem.un
IL_0016: ldc.i4.0
IL_0017: ceq
IL_0019: stloc.3
IL_001a: ldloc.1
IL_001b: ldloc.3
IL_001c: brtrue.s IL_0021

@breyed
Copy link
Author

breyed commented Nov 5, 2015

Note that the verbose IL in the multiple line case is not necessarily incorrect, since the C# compiler could rely on the JIT compiler to optimize out isMultipleOf16. The problem is that currently, neither stage is performing the optimization.

@agocke
Copy link
Member

agocke commented Nov 5, 2015

@breyed Yup, that IL looks fine to me -- it should get picked up by the JIT. I can't reproduce your performance issue on .NET 4.6, though -- it looks like the second one is sometimes faster.

@breyed
Copy link
Author

breyed commented Nov 5, 2015

Analyzing performance gets complicated because code alignment is an issue (and a separate optimization opportunity!). See the comment at the start of Main() in the repro project for more details.

Fortunately, looking at the machine code is straightforward. At the end of the day, if it isn't identical with or without the local variable, there's a problem (makes for easy unit testing :-).

@agocke
Copy link
Member

agocke commented Nov 5, 2015

@breyed So have you verified that the machine code is different on 4.6 between the two assemblies?

@gafter
Copy link
Member

gafter commented Nov 8, 2015

@gafter gafter closed this as completed Nov 8, 2015
@gafter gafter added the Resolution-External The behavior lies outside the functionality covered by this repository label Nov 8, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Resolution-External The behavior lies outside the functionality covered by this repository
Projects
None yet
Development

No branches or pull requests

4 participants