For reasons I can't fully justify, I decided to see just what, if anything, happens when you do loop hoisting yourself.
using System; using System.Collections.Generic; class Program { static void Main(string[] args) { List<int> ints = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); int j = ints.Count; for (int i = 0; i < j; i++) { Console.WriteLine(ints[i]); } Console.ReadLine(); } } //and static void Main(string[] args) { List<int> ints = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); for (int i = 0; i < ints.Count; i++) { Console.WriteLine(ints[i]); } Console.ReadLine(); } //Compiled: $ csc hoist.cs /debug:pdbonly /o+ /platform:x86
So, I take it down to IL and find that the difference in IL is pretty straightforward, saving a callvirt in each iteration of the loop - just what you'd expect. Here's the line that's cut off:
IL_0019: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()
But here's the thing. I read somewhere that the JIT compiler will optimize these things. So for no particurally good reason, I decided to go read assembly. Only by the grace of Mike Dimmick's excellent blog post was I able to learn enough WinDbg to do this. The commands in WinDbg, for reference, were the following, although you'll of course have to adjust the memory locations.
.loadby sos.dll mscorwks !dumpdomain !dumpmodule -mt 01d42c5c !dumpmt -md 01d43014 !U 02320070
Here's where things get interesting! In the image below I cleaned up and manipulated things to produce a clean diff image. Removed memory addresses and the like and replaced them with labels. You won't see this exactly from the !U WinDbp dump, but it'll be close.
You can see the differences in the assembly and that ultimatly, they amount to nothing! I'll prove it.
- The blue circle at the top of the hoisting demonstrates a single memory read and write.
- The red X's cancel each other out, they're both memory reads inside the loop.
- The red circle is an extra memory read inside the loop.
So what did we learn? Pre-mature optimization is a bad idea. What? You already knew that? Well then this is more proof you didn't need, and a fun exercise for me then. Good night.
This blog post would not have been possible without the following resources: Mike Dimmick's aforementioned blog post on WinDbg, endeavormac's excellent x86 Assembly for C Programmers blog post that helped me brush up on Assembly, this awesome opcode cheat sheet, and two old usenet postings.
required, hidden, gravatared
required, markdown enabled (help)
* item 2
* item 3
are treated like code:
if 1 * 2 < 3:
print "hello, world!"
are treated like code: