Will my compiler ignore useless code?
Asked Answered
F

9

40

I've been through a few questions over the network about this subject but I didn't find any answer for my question, or it's for another language or it doesn't answer totally (dead code is not useless code) so here's my question:

Is (explicit or not) useless code ignored by the compiler?

For example, in this code:

double[] TestRunTime = SomeFunctionThatReturnDoubles;
// A bit of code skipped
int i = 0;
for (int j = 0; j < TestRunTime.Length; j++)
{

}
double prevSpec_OilCons = 0;

will the for loop be removed?

I use and


The background is that I maintain a lot of code (that I didn't write) and I was wondering if useless code should be a target or if I could let the compiler take care of that.

Fie answered 24/6, 2015 at 12:49 Comment(14)
No, it wont. For some obscure reason .Length could have side-effects (if not an array)Derisive
I'm 99.9% certain that the code does NOT get ignored, and it will run. So if that TestRunTime.Length is like long.MaxValue, then it would be waiting there for a while.Chaqueta
Compile it, look at the exe in ILSpy/ReflectorFanchan
Try running the debugger. If the debugger runs through the code then it isn't ignored.Bes
@Bes debug mode doesn't use optimisations => I can't unfortunatelyFie
@Derisive Any property could have side effects, therefore an optimizing compiler must not remove it.Quiteris
You can test what the compiler does in these simple cases using Try Roslyn.Schramm
@Matthew: if it is known sealed class eg an array, the behavior of Length is known (and I believe is only computed once then, albeit by the JIT, the compiler will not remove the code)Derisive
@Derisive I never knew the optimizer was that thorough, thanks for the info.Quiteris
@AlexK. That will only tell you what the C# compiler does. But when it comes to optimizations, the what the JIT compiler does is much more important.Chirrupy
Even if it does, you should not ignore useless code. It's useless, and therefore confusing. Avoiding the confusion is well worth the effort.Standfast
@Thomas Alternatively, you can turn the code into a comment. That's what I did for one of my projects (ended up not using the code, but still wanted to keep it in there in case I decided to use it)Pankey
Useless code should be a target for a whole other reason - readability, useless code is some of the most confusing code in the world since when we approach code we're trying to figure out what it does and we do so assuming it does something.Rodriques
related Is there a way to see the native code produced by theJITter for given C# / CIL?Florineflorio
F
1

I've made a little form to test it according to a few answerers ideas about using long.MaxValue, here's my reference code:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    myTextBox.Text = test.Elapsed.ToString();
}

and here's the code with kinda useless code:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < int.MaxValue; i++)
    {
    }
    myTextBox.Text = test.Elapsed.ToString();
}

You'll remark that I used int.MaxValue instead of long.MaxValue, I didn't want to spend the year day on this one.

As you can see:

---------------------------------------------------------------------
|                   |   Debug               |   Release             |
---------------------------------------------------------------------
|Ref                |   00:00:00.0000019    |   00:00:00.0000019    |
|Useless code       |   00:00:05.3837568    |   00:00:05.2728447    |
---------------------------------------------------------------------

The code isn't optimized. Hang on a bit, I'll try with some int[] to test int[].Lenght:

public Form1()
{
    InitializeComponent();
    int[] myTab = functionThatReturnInts(1);

    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < myTab.Length; i++)
    {

    }
    myTextBox.Text = test.Elapsed.ToString();
}
public int[] functionThatReturnInts(int desiredSize)
{
    return Enumerable.Repeat(42, desiredSize).ToArray();
}

And here's the results:

---------------------------------------------
|   Size            |   Release             |
---------------------------------------------
|             1     |   00:00:00.0000015    |
|           100     |   00:00:00            |
|        10 000     |   00:00:00.0000035    |
|     1 000 000     |   00:00:00.0003236    |
|   100 000 000     |   00:00:00.0312673    |
---------------------------------------------

So even with arrays, it doesn't get optimized at all.

Fie answered 25/6, 2015 at 13:50 Comment(0)
D
27

Well, your variables i and prevSpec_OilCons, if not used anywhere will be optimized away, but not your loop.

So if your code looks like:

static void Main(string[] args)
{
    int[] TestRunTime = { 1, 2, 3 };
    int i = 0;
    for (int j = 0; j < TestRunTime.Length; j++)
    {

    }
    double prevSpec_OilCons = 0;
    Console.WriteLine("Code end");
}

under ILSpy it will be:

private static void Main(string[] args)
{
    int[] TestRunTime = new int[]
    {
        1,
        2,
        3
    };
    for (int i = 0; i < TestRunTime.Length; i++)
    {
    }
    Console.WriteLine("Code end");
}

Since the loop has couple of statements, like comparison and increment, it could be used for implementing somewhat short delay/wait period. (although not a good practice to do so).

Consider the following loop, which is an empty loop, but it will take a lot of time to get executed.

for (long j = 0; j < long.MaxValue; j++)
{

}

The loop in your code, is not a dead code, as far as dead code is concerned, the following is a dead code, and will be optimized away.

if (false)
{
    Console.Write("Shouldn't be here");
}

The loop, will not even be removed by the .NET jitters. Based on this answer

Denti answered 24/6, 2015 at 13:1 Comment(9)
"...it could be used for implementing somewhat short delay/wait period..." if execution time would be a desirable feature, no performance optimization would be allowed by the compiler.Paolapaolina
@StefanSteinegger, you are right, my statement was with respect to this particular loop, since it has two statements which are actually getting executed, and could have side effects.Denti
Ok so since my TestRunTime.Length may be somewhat long, I should remove it?Fie
@Thomas, sure, I don't think it would be long enough to cause a delay (or specifically have been placed for the delay). If you have unit test, then it would be easier for you to test the change, otherwise this looks like harmless, IMO.Denti
Right now, he's looping off of properties, which could be doing something internally when they're invoked. What if he hard-codes an int literal, instead of TestRunTime.Length or long.MaxValue? Will it still keep the for loop?Eer
@Panzercrisis, yes it will, I checked it in ILSpy before posting in the answer, so for (long j = 0; j < long.MaxValue; j++){} will be left in the code, and it will not be optimized away.Denti
I mean, what if he says for (long j = 0; j < 3; j++){}? I'm figuring it'll probably still be there, but I guess there may be a small chance.Eer
@Panzercrisis, it is there, I can see in the ILSpy.Denti
Just because it's not optimized away by the IL doesn't mean it won't be optimized away in the JIT compiler that can prove a lot more things.Rodriques
P
15

The loop can't be removed, the code is not dead, for instance:

  // Just some function, right?
  private static Double[] SomeFunctionThatReturnDoubles() {
    return null;
  }

  ...
  double[] TestRunTime = SomeFunctionThatReturnDoubles();
  ...
  // You'll end up with exception since TestRunTime is null
  for (int j = 0; j < TestRunTime.Length; j++)
  {
  }
  ...

Usually, compiler just can't predict all possible outcomes of SomeFunctionThatReturnDoubles and that's why it preserves the loop

Pavilion answered 24/6, 2015 at 14:11 Comment(5)
The either the C# compiler or the JIT could legitimately optimize the loop to int len = TestRunTime.Length; for (int j=0; j<len; j++) {} (it would be a simple optimization even for the JIT); from there it could easily be optimized to TestRunTime.Length;. Not quite totally eliminating it, but making it go much faster.Meany
@Meany That's not the same. You can't know that the Length property does nothing else. If for example the Length keeps track of how many times the get {} is called before returning the result your optimization would differ from what is actually expected. The compiler has to be absolutely certain that there are no side effects before it can optimize.Portaltoportal
@drake7707: For types other than arrays, that would be true. Both C# and the Runtime know what arrays are; the generated machine code to access an element of a single-dimensional zero-based array doesn't call an "access element" subroutine, but instead checks the index against the length, throws an exception if out of bounds, and otherwise fetches the data directly. In addition, if the JIT can determine the maximum subscript could will be accessed within a loop, it can from what I've read check the length before the loop and, if acceptable, omit checks within the loop.Meany
@drake7707: If the JIT can hoist array-bounds tests which are done for subscript validation, I see no reason it shouldn't be just as capable of hoisting length requests which are made for other reasons.Meany
@Meany Ah yes, I forgot about the array bounds check optimization. Well I guess if the compiler can deduce from the IL (like no stloc or stfld etc) that it's pure read only I see no reason why it couldn't (and in this case why it shouldn't) optimize the snippet above.Portaltoportal
I
6

In your loop there are two an operation implicit in each iteration. An increment:

j++;

and comparison

j<TestRunTime.Length;

So, the loop is not empty although it looks like it is. There is something being executed at the end and this is not ignored by the compiler of course.

This happens also in other loops.

Improvised answered 24/6, 2015 at 12:54 Comment(0)
M
4

It won't be ignored. However, when you get to IL there will be a jump statement, so the for will run as if it was an if statement. It will also run the code for ++ and length, as @Fleve mentioned. It will just be extra code. For readability sake, as well as to keep with code standards I would remove the code if you don't use it.

Marketing answered 24/6, 2015 at 12:56 Comment(0)
S
4

Is (explicit or not) useless code ignored by the compiler?

You can't easily determine it's useless, so the compiler can't either. The getter of TestRunTime.Length can have side-effects, for example.

The background is that I maintain a lot of code (that I didn't write) and I was wondering if useless code should be a target

Before you refactor a piece of code, you have to verify what it does in order to be able to change it and say afterwards it still has the same result. Unit tests are a great way of doing this.

Sleety answered 24/6, 2015 at 13:6 Comment(3)
(I've edited my question but in short:) Does Double[].Length have side effect?Fie
Find out for yourself: What is a "side effect". Array.(get_)Length has no side effect, it just returns the array's length.Sleety
Nice link, so no it doesn't. ThanksFie
Y
4

The JIT is basically capable of removing dead code. It is not very thorough. Dead variables and expressions are reliably killed. That is an easy optimization in SSA form.

Not sure about control flow. If you nest two loops only the inner one will be deleted I remember.

If you want to find out for sure what is deleted and what not look at the generated x86 code. The C# compiler does very few optimizations. The JIT does a few.

The 4.5 32 and 64 bit JITs are different code bases and have different behavior. A new JIT (RyuJIT) is upcoming that in my testing generally does worse, sometimes better.

Ypsilanti answered 24/6, 2015 at 13:7 Comment(0)
G
4

Obviously your compiler will not ignore useless code, but analyse it carefully and then try to remove it, if it performs optimisations.

In your case, the first interesting thing is whether the variable j is used after the loop or not. The other interesting thing is TestRunTime.Length. The compiler will look at it and check whether it always returns the same result, and if yes whether it has any side effects, and if yes whether calling it once has the same side effect in total as calling it repeatedly.

If TestRunTime.Length has no side effect and j is not used then the loop is removed.

Otherwise, if calling TestRunTime.Length repeatedly has more side effects than calling it once, or if repeated calls return different values, then the loop must be executed.

Otherwise, j = max (0, TestRunTime.Length).

Next, the compiler can determine whether the assignment TestRunTime.Length is needed. It may be replaced with code that just determines what TestRunTime.Length would be.

Then of course your compiler might not try any fancy optimisations, or the language rules might be so that it cannot determine these things, and you are stuck.

Groark answered 24/6, 2015 at 16:35 Comment(2)
variable j is used after the loop or not. it can't, it's declared inside the loop (or I misread a few stuff)Fie
If TestRunTime.Length has no side effect and j is not used then the loop is removed. => Nop, see my answer below...Fie
D
3

For the most part, you shouldn't worry about proactively removing useless code. If you run into performance issues, and your profiler says that some useless code is eating your clock cycles, then go nuclear on it. However, if the code truly does nothing and has no side effects, then it'll probably have little impact on running time.

That said, most compilers are not required to perform any optimizations, so relying on compiler optimizations is not always the smartest option. In many cases though, even a useless spin loop can execute pretty fast. A basic spinlock that loops one million times would compile into something like mov eax, 0 \ inc eax \ cmp eax, 1000000 \ jnz -8. Even if we discounted on-CPU optimizations, that's only 3 cycles per loop (on a recent RISC style chip) since there is no memory access, so there won't be any cache invalidation. On a 1GHz CPU, that's only 3,000,000/1,000,000,000 seconds, or 3 milliseconds. That would be a pretty significant hit if you tried running it 60 times a second, but in many cases, it probably won't even be noticeable.

A loop like the one I described would almost definitely be peephole optimized to mov eax 1000000, even in a JIT environment. It would likely be optimized further than that, but given no other context, that optimization is reasonable and would cause no ill effects.

tl;dr: If your profiler says that dead/useless code is using a noticeable amount of your run-time resources, remove it. Don't go on a witch hunt for dead code though; leave that for the rewrite/massive refactor down the line.

Bonus: If the code generator knew that eax wasn't going to be read for anything other than the loop condition and wanted to retain the spinlock, it could generate mov eax, 1000000 \ dec eax \ jnz -3 and reduce the penalty of the loop by a cycle. Most compilers would just remove it entirely though.

Davidoff answered 24/6, 2015 at 17:23 Comment(3)
I don't like the idea "you have a fast CPU, so don't worry, let it stay". The lack of microoptimisation leads to requirement of having faster processor, more RAM etc. "But it doesn't matter". Yes, it does, if one uses older computer, has few memory. Why not help him? Because it is not worth effort? I believe many programs could be smaller, adjusted to older computers, but of course it needs effort from the coders. For some reason we expect to throw away a computer and replace it with new one every few years. Why? Commodore 64 had 64K RAM and it had even its office suite.Antonia
Last year I worked in video decoding, paying attention to all this littles "It's not that important" I went from HD @ ~55fps playing to 4K @60fps fluent. So yeah it does matterFie
I will say that I do a bit of embedded development, and I am firmly in the camp that micro-optimization is good. I also did say that "That would be a pretty significant hit if you tried running it 60 times a second" which was a direct acknowledgement that if you are doing significant data crunching, such as video co/decing or game logic, then any (micro)optimizations will matter. The answer was more tuned towards the asker, where performance doesn't seem to be a significant issue as this code has already accumulated in the codebase, and putting man-hours in to fix it might not be an option.Davidoff
F
1

I've made a little form to test it according to a few answerers ideas about using long.MaxValue, here's my reference code:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    myTextBox.Text = test.Elapsed.ToString();
}

and here's the code with kinda useless code:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < int.MaxValue; i++)
    {
    }
    myTextBox.Text = test.Elapsed.ToString();
}

You'll remark that I used int.MaxValue instead of long.MaxValue, I didn't want to spend the year day on this one.

As you can see:

---------------------------------------------------------------------
|                   |   Debug               |   Release             |
---------------------------------------------------------------------
|Ref                |   00:00:00.0000019    |   00:00:00.0000019    |
|Useless code       |   00:00:05.3837568    |   00:00:05.2728447    |
---------------------------------------------------------------------

The code isn't optimized. Hang on a bit, I'll try with some int[] to test int[].Lenght:

public Form1()
{
    InitializeComponent();
    int[] myTab = functionThatReturnInts(1);

    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < myTab.Length; i++)
    {

    }
    myTextBox.Text = test.Elapsed.ToString();
}
public int[] functionThatReturnInts(int desiredSize)
{
    return Enumerable.Repeat(42, desiredSize).ToArray();
}

And here's the results:

---------------------------------------------
|   Size            |   Release             |
---------------------------------------------
|             1     |   00:00:00.0000015    |
|           100     |   00:00:00            |
|        10 000     |   00:00:00.0000035    |
|     1 000 000     |   00:00:00.0003236    |
|   100 000 000     |   00:00:00.0312673    |
---------------------------------------------

So even with arrays, it doesn't get optimized at all.

Fie answered 25/6, 2015 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.