I've been investigating today different effects of some csc.exe (C# compiler) compiler switches and came up with the really interesting conclusion
CSC.exe
C# compiler compiler has two important switches I pick for my today's investigation:
-
optimize, with values -/ +
-
debug, with values -/+/full/pdbonly (+ and full are the same setting)
When you create a new C# project in Visual Studio;
- Debug configuration is set up to use: /optimize- /debug:full
- Release configuration is set up to use /optimize+ /debug:pdbonly
(To see it for yourself check out Output window after starting the build in release and debug build)
What I tested
I pick four different scenarios for my little experiment:
- /optimize- /debug:full (debug configuration of visual studio )
- /optimize+ /debug:full (optimized code with complete debug info setting)
- /optimize+ /debug:pdbonly (release configuration of visual studio)
- /optimize+ (typical "release" build setting used while building manually - command prompt, Nant etc)
How did I test it
I was using in my tests very simple console application which only purpose is to have a property (to check optimization) and to throw an exception (to check stack trace)
using System;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
int i = 5 / Convert.ToInt32(Test);
}
private static string _test;
public static string Test
{
get { return _test; }
set { _test = value; }
}
}
}
After every build I was:
-
Checking out for IL code optimization using ILDASM.exe to check out if the property code has been inlined and that there are no NOP IL statement at the beginning )
-
Checking if stack trace shows the line number
-
Checking if debugger can be attached to process
Test 1: /optimize- /debug:full
This one was just done for warming up, because we all know what debug configuration in visual studio does
Command line: csc /t:exe /debug:full /optimize- program.cs
File data
TestApp.exe 4608 bytes
TestApp.pdb 13824 bytes
Optimization check
As you can see there are 10 lines of code and IL_0000 contains NOP. Clearly, not optimized IL code
Debugger check
I succeed in attaching debugger to cp
Stack trace check
System.DivideByZeroException was unhandled
Message="Attempted to divide by zero."
Source="TestApp"
StackTrace:
at TestApp.Program.Main(String[] args) in D:\Documents\Documents\Blog material\CSC\TestApp\TestApp\Program.cs:line 9
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
There is line number data in stack trace, which is also expected
Test 2: /optimize+ /debug:full
File data
TestApp.exe 4096 bytes
TestApp.pdb 13824 bytes
Optimization check
IL code is optimized
Stack Trace
System.DivideByZeroException was unhandled
Message="Attempted to divide by zero."
Source="TestApp"
StackTrace:
at TestApp.Program.Main(String[] args) in D:\Documents\Documents\Blog material\CSC\TestApp\TestApp\Program.cs:line 9
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
So the stack trace shows line number.
Test 3: /optimize+ /debug:pdbonly
Command line: csc /t:exe /debug:pdbonly /optimize+ program.cs
File data
Program.exe 4096 bytes
Program.pdb 11776 bytes
Optimization check
As we can see on the ILDASM screen shoot code is optimized (property is inlined and no NOP)
Stack trace
System.DivideByZeroException was unhandled
Message="Attempted to divide by zero."
Source="TestApp"
StackTrace:
at TestApp.Program.Main(String[] args) in D:\Documents\Documents\Blog material\CSC\TestApp\TestApp\Program.cs:line 9
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
There is a line number in stack trace. Great!
Test 4: /optimize+ /debug-
Command line: csc /t:exe /debug:pdbonly /optimize+ program.cs
File data
Program.exe 3584 bytes
Program.pdb N/A
Optimization check
Results totally expected for the release build - optimized IL code
Stack trace
at TestApp.Program.Main(String[] args)
No stack trace information
Conclusions
Test result analysis
Test case 4 (release build with debug- switch produces the smallest program.exe: 3584 bytes)
Test 2 (debug:full) and Test 3 (debug:pdbobnly) with optimize+ produced optimized code but the assembly file size increased to 4096 bytes
The size increase comes from the additional assembly manifest declaration of the DebuggableAttribute; IL code is exactly the same in test cases cases 2,3,4.
DebuggableAttribute is initialized with OR values of DebuggingModes enumeration
[Flags]
public enum DebuggingModes
{
Default = 1,
DisableOptimizations = 0x100,
EnableEditAndContinue = 4,
IgnoreSymbolStoreSequencePoints = 2,
None = 0
}
If we would take a look at test cases 2 and 3 with ILDASM we would notice that both of them have in their manifests
debug: pdbonly
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 )
debug:full
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 03 00 00 00 00 00 )
Or if we would visualize the same initialized flag values by using Reflector
debug:pdbonly
debug:full
The difference between the test case 2 (full) and test case 3 (pdbonly) is that with full debug switch compiler initialize the Debuggable attribute with DebuggingModes.Default value.
According to the MSDN: DebuggableAttribute.DebuggingModes Enumeration:
DebuggingModes.Default value
"Instructs the just-in-time (JIT) compiler to use its default behavior, which includes enabling optimizations, disabling Edit and Continue support, and using symbol store sequence points if present."
DebuggingModes.IgnoreSymbolStoreSequencePoints
"Sequence points are used to indicate locations in the Microsoft intermediate language (MSIL) code that a debugger user will expect to be able to refer to uniquely, such as for setting a breakpoint. The JIT compiler ensures it does not compile the MSIL at two different sequence points into a single native instruction. By default, the JIT compiler examines the symbol store in the program database (PDB) file for a list of additional sequence points. However, loading the PDB file requires that the file be available and has a negative performance impact. In version 2.0, compilers can emit "implicit sequence points" in the MSIL code stream through the use of MSIL "nop" instructions. Such compilers should set the IgnoreSymbolStoreSequencePoints flag to notify the common language runtime to not load the PDB file."
According to Rick Byers - DebuggingModes.IgnoreSymbolStoreSequencePoints this flag setting informs the compiler to use all available space in code (places where IL evaluation stack is empty, or on any "nop" instruction is generated) to store implicit sequence points (internal pointers to PDB locations) into the resulting assembly which results with performance boost of the release build.
Final conclusion on test cases
There is no significant differences between the test cases 2 (full) and 3 (pdbonly) from the performance perspective
How-To:Releasing the builds
Build all of your release builds with /optimize+ /debug:pdbonly switch.
1) Your IL code would be optimized, so no performance hit would occur
2) The stack class would get access to the line information, and make your release builds "debug like"
3) The visual studio IDE release configuration default setting is set to that value
4) I couldn't find any information on net that effects of emitting DebuggableAttributes to assembly manifest causes any significant performance in NET 2.0