.NET Forum / .NET Framework / CLR / March 2007
Redundant IL generated by the compiler
|
|
Thread rating:  |
Anthony Paul - 11 Jan 2007 23:40 GMT Hello everyone,
I've been teaching myself IL and when compiling a very simple example I found what I believe to be redundant code generated by the C# compiler. Here is my code :
using System;
namespace TestIL { class Program { static int val;
static void Main(string[] args) { bool res;
do { Console.WriteLine("Enter a number"); string s = Console.ReadLine(); res = int.TryParse(s, out val);
if (res) { if ((val & 1) != 0) s = "odd!"; else s = "even!"; } else s = "How Rude!";
Console.WriteLine(s); } while (res); } } }
and the IL code generated is :
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 72 (0x48) .maxstack 2 .locals init ([0] bool res, [1] string s) IL_0000: ldstr "Enter a number" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: call string [mscorlib]System.Console::ReadLine() IL_000f: stloc.1 // <-- redundant? IL_0010: ldloc.1 // <-- redundant? IL_0011: ldsflda int32 TestIL.Program::val IL_0016: call bool [mscorlib]System.Int32::TryParse(string, int32&) IL_001b: stloc.0 IL_001c: ldloc.0 IL_001d: brfalse.s IL_0038 IL_001f: ldsfld int32 TestIL.Program::val IL_0024: ldc.i4.1 IL_0025: and IL_0026: brfalse.s IL_0030 IL_0028: ldstr "odd!" IL_002d: stloc.1 // <-- redundant? IL_002e: br.s IL_003e IL_0030: ldstr "even!" IL_0035: stloc.1 // <-- redundant? IL_0036: br.s IL_003e IL_0038: ldstr "How Rude!" IL_003d: stloc.1 // <-- redundant? IL_003e: ldloc.1 // <-- redundant? IL_003f: call void [mscorlib]System.Console::WriteLine(string) IL_0044: ldloc.0 IL_0045: brtrue.s IL_0000 IL_0047: ret } // end of method Program::Main
Please correct me if I'm wrong, but in the above IL code I commented the lines that I believe are redundant; I just can't see any use for them. Would anyone happen to know why they are being generated?
Cheers!
Anthony
Jon Shemitz - 11 Jan 2007 23:51 GMT > IL_000f: stloc.1 // <-- redundant? > IL_0010: ldloc.1 // <-- redundant?
> IL_0028: ldstr "odd!" > IL_002d: stloc.1 // <-- redundant?
> IL_0030: ldstr "even!" > IL_0035: stloc.1 // <-- redundant?
> IL_0038: ldstr "How Rude!" > IL_003d: stloc.1 // <-- redundant? > IL_003e: ldloc.1 // <-- redundant?
> Please correct me if I'm wrong, but in the above IL code I commented > the lines that I believe are redundant; I just can't see any use for > them. Would anyone happen to know why they are being generated? Perhaps debugger cues; perhaps just because it's easier to write the code generator that way.
In any case, I wouldn't pay much attention to it - as you (presumably) know, CIL is just the source code to the jitter, and the jitter is quite capable of optimizing out unnecessary saves and loads.
 Signature .NET 2.0 for Delphi Programmers www.midnightbeach.com/.net
Ben Voigt - 11 Jan 2007 23:51 GMT > Please correct me if I'm wrong, but in the above IL code I commented > the lines that I believe are redundant; I just can't see any use for > them. Would anyone happen to know why they are being generated? Yes, you'll see that a lot. Did you enable release build? Even then, most optimization is done by the JIT converting from MSIL to machine code, so the MSIL tends to be poorly optimized (and therefore .NET assemblies are larger than they should be, by probably some 10-25% judging from the frequency of occurence of that particular useless pattern).
The why, is because the parser constructs an abstract syntax tree, the compiler does name binding and overload resolution, and then converts the results to MSIL. The AST representation has to be very flexible, so in most situations there's some redundancy.
> Cheers! > > Anthony Anthony Paul - 12 Jan 2007 00:22 GMT > The why, is because the parser constructs an abstract syntax tree, the > compiler does name binding and overload resolution, and then converts the > results to MSIL. The AST representation has to be very flexible, so in most > situations there's some redundancy. Hmm... I don't see how AST generation would result it the kind of redundancy exhibited in the IL above but I'm no expert in that field so I'll take your word for it. Also, you would think that the post-processor would eliminate them... seems so simple. Anyway, I got my answer, thanks to all for replying!
Cheers!
Anthony
p.s. Oh, and yes, I targetted it for a release build as soon as I saw all the NOP's generated in the debug build, lol! I read somewhere that they were intentionally inserted to help with debugging.
Ben Voigt - 12 Jan 2007 17:04 GMT >> The why, is because the parser constructs an abstract syntax tree, the >> compiler does name binding and overload resolution, and then converts the [quoted text clipped - 7 lines] > post-processor would eliminate them... seems so simple. Anyway, I got > my answer, thanks to all for replying! As Jon Shemitz mentioned, the expression trees can't be merged, to maintain line number information the AST for each line needs to be independently output.
Also, the MS developers are constantly blogging that the JIT recognizes certain patterns and performs special handling for them... so I fear that hand-optimized MSIL, though substantially smaller, may cause the JIT optimizer to miss certain optimizations.
> Cheers! > [quoted text clipped - 3 lines] > all the NOP's generated in the debug build, lol! I read somewhere that > they were intentionally inserted to help with debugging. Jon Skeet [C# MVP] - 12 Jan 2007 00:08 GMT > I've been teaching myself IL and when compiling a very simple example I > found what I believe to be redundant code generated by the C# compiler. > Here is my code : <snip>
Is your point that the value in local 1 is only ever used immediately after it's stored, and that therefore it could just stay on the stack instead of being copied into the variable at all?
Consider what you'd see in a debugger if you didn't have the stloc and ldloc instructions - s would never have a value.
Of course, I may have missed your point entirely - I'm somewhat sleepy.
The last three stloc.1 calls certainly could have been amalgamated into one though, by branching before storing instead of after. That may violate some other rules though...
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Anthony Paul - 12 Jan 2007 00:31 GMT > Is your point that the value in local 1 is only ever used immediately > after it's stored, and that therefore it could just stay on the stack > instead of being copied into the variable at all? My point is that, given the IL code in my original post, if you were to strip it of the lines which I marked as being redundant, the code will compile and execute just fine and as intended. The answer to my question happens to be that the compiler is inneficient and is apt to produce some bloat that the JIT will eventually sort out.
Cheers!
Anthony
Jon Skeet [C# MVP] - 12 Jan 2007 22:37 GMT > > Is your point that the value in local 1 is only ever used immediately > > after it's stored, and that therefore it could just stay on the stack [quoted text clipped - 5 lines] > question happens to be that the compiler is inneficient and is apt to > produce some bloat that the JIT will eventually sort out. It will compile and execute - but if you try to use it in a debugger, you'll never see a value in s, I believe. When you're debugging, do you really want to not see assignments to locals?
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Anthony Paul - 12 Jan 2007 23:13 GMT I would love to have that information *IF* I compile a debug build. A release build should never contain code to ease debugging IMHO. However, I don't think that these lines of IL code were generated by the compiler with ease--of-debugging in mind.
Cheers!
Anthony
> It will compile and execute - but if you try to use it in a debugger, > you'll never see a value in s, I believe. When you're debugging, do you [quoted text clipped - 4 lines] > http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet > If replying to the group, please do not mail me too Jon Skeet [C# MVP] - 12 Jan 2007 23:29 GMT > I would love to have that information *IF* I compile a debug build. A > release build should never contain code to ease debugging IMHO. > However, I don't think that these lines of IL code were generated by > the compiler with ease--of-debugging in mind. If the C# compiler team knows (for certain) that the JIT will optimise away those statements when there is no debugger attached, what's the downside of making it easier to debug even in release mode?
I would imagine that it makes for simpler compiler code - and given that you'll end up with multiple high level language compilers but only one JIT, I'd rather the optimisation went into the JIT than the high level language compilers.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Anthony Paul - 13 Jan 2007 00:33 GMT I agree with you that the emphasis should be on the JIT when it comes to optimization; however, I'm a firm believer of doing things right the first time around. I've read another thread (in which you've been involved) that was rather interesting regarding this very subject and am aware that perhaps the MSIL shouldn't be highly optimized (ie. loop unrolling) because the JIT would do a better job of it seeing that it's aware of the underlying platform. I can see that now and though I'm not in the position to agree or disagree I see that there may be undesired consequences and that's good enough for me. However, in this specific case I see redundancy in the code that has nothing to do with optimization... it's just redundant, dead code that does nothing but contribute to bloat. It's a minor thing really, maybe I'm just being anal about it but this is probably due to my having learned how to program in a time when one would drool all over 32K of memory, 1 mhz processors were the "bomb", and data was stored on tape or floppies. Ahhh, the good ol' days!! :D
Cheers!
Anthony
p.s. Oh, and shame on you! The C# compiler team should NEVER make any assumptions! :D
> If the C# compiler team knows (for certain) that the JIT will optimise > away those statements when there is no debugger attached, what's the [quoted text clipped - 9 lines] > http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet > If replying to the group, please do not mail me too Jon Skeet [C# MVP] - 13 Jan 2007 19:13 GMT <snip>
> p.s. Oh, and shame on you! The C# compiler team should NEVER make any > assumptions! :D Except that you're asking them to make the assumption that you don't want the value of the local variable to be updated in the IL when it *was* updated in the code :)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Ciaran O''Donnell - 12 Feb 2007 12:05 GMT I agree in doing things right the first time to if you wanted this done 'correctly' in IL, perhaps you should have coded it that way as you knew that you didnt need the s variable. If you remove the first assignment to s and put the readline call as the param to the parse call then the IL is removed. The compiler is doing what you told it to do. So if you are that concerned with doing it right first time and not wasting time compiling those statements and storing them, you need to optimise your code. The compiler cant as you could still attach debuigging components and expect to see a value in the field s.
 Signature Ciaran O''Donnell http://wannabedeveloper.spaces.live.com
> I agree with you that the emphasis should be on the JIT when it comes > to optimization; however, I'm a firm believer of doing things right the [quoted text clipped - 33 lines] > > http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet > > If replying to the group, please do not mail me too Anthony Paul - 23 Feb 2007 15:20 GMT Hello Ciaran!
Thanks for replying!
> code. The compiler cant as you could still attach debuigging components and > expect to see a value in the field s. But see, that's my point... debugging was explicitly disabled. At this point there should be no assumptions as to whether I want to see debugging info or not (in this case seeing the value for 's'). IMHO a release build should be completely devoid of any debugging information whatsoever for performance reasons and compactness. However, I believe my question has been answered... the reason why redundant IL code is generated is for debugging purposes, even in a release build.
Anthony
Willy Denoyette [MVP] - 12 Jan 2007 11:23 GMT > Hello everyone, > [quoted text clipped - 83 lines] > > Anthony Did you actually try to remove what you believe to be redundant and run peverify.exe on the resultant assembly?
Willy.
Anthony Paul - 12 Jan 2007 13:09 GMT > Did you actually try to remove what you believe to be redundant and run peverify.exe on the > resultant assembly? > > Willy. I hadn't dont that before since I was unaware of the peverify tool, but I just ran it and everything is kosher.
Cheers!
Anthony
Link_M - 27 Mar 2007 16:50 GMT IL_0030: ldstr "even!" IL_0035: stloc.1 // <-- redundant? IL_0036: br.s IL_003e IL_0038: ldstr "How Rude!" IL_003d: stloc.1 // <-- redundant? IL_003e: ldloc.1 // <-- redundant? IL_003f: call void [mscorlib]System.Console::WriteLine(string) IL_0044: ldloc.0 IL_0045: brtrue.s IL_0000 IL_0047: ret
In this case i don´t think is redundant, because the IL is saving the variable S on stack.
but you can create your il code, by copying that text generated and run on ILDasm, and put ou delete code, and see what is happening!
I start to see il, but i think is like this!
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|