Is there a way to get the .Net JIT or C# compiler to optimize away empty for-loops?
Asked Answered
K

1

11

A followup to Does .NET JIT optimize empty loops away?:

The following program just runs an empty loop a billion times and prints out the time to run. It takes 700 ms on my machine, and I'm curious if there's a way to get the jitter to optimize away the empty loop.

using System;

namespace ConsoleApplication1 {
    class Program {
        static void Main() {
            var start = DateTime.Now;
            for (var i = 0; i < 1000000000; i++) {}
            Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
        }
    }
}

As far as I can tell the answer is no, but I don't know if there are hidden compiler options I might not have tried. I have made sure to compile in release mode and run with no debugger attached, but still 700 ms is being taken to run this empty loop. I also tried NGEN with the same result (though my understanding is that it should produce the same compiled code as the JIT anyway, right?). However I've never used NGEN before and may be using it wrong.

It seems like this would be something easy for the JIT to find and optimize away, but knowing very little about how jitters work in general, I'm curious if there's a specific reason this optimization would have been left out. Also the VC++ compiler definitely does seem to make this optimization, so I wonder why the discrepancy. Any ideas?

Kirkkirkcaldy answered 2/9, 2011 at 19:35 Comment(12)
Why is this an issue for you in reality? If you've got an empty loop, just get rid of it.Livialivid
How is this different than the question you're referencing?Crave
@Reed I know java has JIT options such as server and client. The reference question doesn't ask anything about options that may cajole the jitter into optimizing this.Kirkkirkcaldy
It isn't an empty loop. It's incrementing i one billion times.Stain
@JonSkeet My curiosity was piqued by this blog post: fsmpi.uni-bayreuth.de/~dun3/archives/…Kirkkirkcaldy
@Dax: that article is bad. The code snippets don't do the same thing (hint: order of operations on floating-point numbers matters). You can't say if apples are faster than oranges.Hess
@Martin while that's true, C# and .net make so little guarantees in the floating point area that I'm pretty sure that the first optimization would be allowed.Chou
The difference manual inlining on the other hand is probably caused PRN being a member variable and not a local variable. It seems like it doesn't optimize out redundant reads to member variables, possibly because a thread might modify it in between. But I don't think the memory model actually guarantees that.Chou
One admittedly unusual use-case for this optimization would be loops that contain only Debug code and therefore are empty in a Release build. That said, it would be odd to have Debug code executing a very large number of times, so any performance impact would be pretty minimal. You can also manually create this optimization by putting the loop in a method and annotating the method so that it only gets called in a Debug build.Gruesome
If a loop was only to contain debug code never to be run in release, then I don't see why you wouldn't use the DEBUG preprocessor directive (which isn't actually preprocessor in C#), or use the ConditionalAttribute("DEBUG"). I don't see why JIT should have to optimize that code (even though it doesn't)Rottweiler
@RMartinhoFernandes: Yes, that's what I noted in the comments of that article.Kirkkirkcaldy
@Dax: Being interested in something just for the sake of it is absolutely fine (good, even) - but I would normally say so explicitly in the question...Livialivid
M
8

No, none of the .NET jitters I know eliminate empty for loops. The exact reason why isn't that clear to me, the jitter optimizer certainly knows how to make optimizations like that and readily eliminates dead code. Check this answer for details. I believe this was intentional, leaving the loop in place for its possibly intended side-effect, consuming time. Which of course only makes sense for very short loops to implement a spinwait.

Moores answered 2/9, 2011 at 19:51 Comment(5)
That's a kind of odd logic though. The entire point in optimizing code is to make it consume less time. If you decide to preserve the amount of spent time, it kind of undermines the entire idea of optimizing.Pencel
A spinwait is an optimization.Moores
Seeing the empty loop reminds me of poorly written old-school DOS programs that used empty loops to introduce delays or relied on CPU timing to enforce program speed. DOS emulators that run really old games sometimes have to purposely throttle CPU execution so that the game won't update at ridiculous frame rates.Gruesome
@jalf: The best I've heard that expressed is along the lines of "If usage of CPU time is included in observable behavior, then all optimization is forbidden".Ryley
@DaxFohl, in spin wait they don't normally use an empty loop: they check for value of some volatile variable, or processory cycle count, or high resolution clock.Iceskate

© 2022 - 2024 — McMap. All rights reserved.