Performance penalty of Debug.Assert in .NET
-
I'm writing some low level C# which is doing bit level operations (shifting/logic) which needs to be very high performance (real time application). Usually, I'd just do some checks and throw an exception if things aren't right, but I can't afford the overhead of that here. Back in the day, in C++ I'd just use assertions for my checks which would cease to exist completely in the release build. In C#, we have Debug.Assert but its use seems greyer to me, because debug vs. release in .NET is greyer. Macros disappear depending on switches, but it seems the only way a Debug.Assert can disappear is if its hardedcoded in the JIT that way. So my question I guess is does the Debug.Assert make it into the MSIL regardless of debug/release, and then does the JIT just remove it for release and remove it completely? What happens with something like this in a release build, does the counter increment?
Debug.Assert(++counter != 100);
Regards, Rob Philpott.
-
I'm writing some low level C# which is doing bit level operations (shifting/logic) which needs to be very high performance (real time application). Usually, I'd just do some checks and throw an exception if things aren't right, but I can't afford the overhead of that here. Back in the day, in C++ I'd just use assertions for my checks which would cease to exist completely in the release build. In C#, we have Debug.Assert but its use seems greyer to me, because debug vs. release in .NET is greyer. Macros disappear depending on switches, but it seems the only way a Debug.Assert can disappear is if its hardedcoded in the JIT that way. So my question I guess is does the Debug.Assert make it into the MSIL regardless of debug/release, and then does the JIT just remove it for release and remove it completely? What happens with something like this in a release build, does the counter increment?
Debug.Assert(++counter != 100);
Regards, Rob Philpott.
See here: Assertions in Managed Code - Visual Studio | Microsoft Docs[^]
Quote:
In Visual Basic and Visual C#, you can use the Assert method from either Debug or Trace, which are in the System.Diagnostics namespace. Debug class methods are not included in a Release version of your program, so they do not increase the size or reduce the speed of your release code.
I'd be tempted to check with ILSpy, but I believe all the assertion code including the condition check are discarded in Release builds.
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
-
I'm writing some low level C# which is doing bit level operations (shifting/logic) which needs to be very high performance (real time application). Usually, I'd just do some checks and throw an exception if things aren't right, but I can't afford the overhead of that here. Back in the day, in C++ I'd just use assertions for my checks which would cease to exist completely in the release build. In C#, we have Debug.Assert but its use seems greyer to me, because debug vs. release in .NET is greyer. Macros disappear depending on switches, but it seems the only way a Debug.Assert can disappear is if its hardedcoded in the JIT that way. So my question I guess is does the Debug.Assert make it into the MSIL regardless of debug/release, and then does the JIT just remove it for release and remove it completely? What happens with something like this in a release build, does the counter increment?
Debug.Assert(++counter != 100);
Regards, Rob Philpott.
I just checked, and Debug.Assert is completely removed from Release code: Code:
private void FrmMain\_Shown(object sender, EventArgs e) { ... string\[\] dataWithCounts = dataFromTextBox.GroupBy(d => d).Select(g =>$"{g.Key}({g.Count()})").ToArray(); Debug.Assert(dataWithCounts.Count() != 3); }
Debug:
IL_008c: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2)
IL_0091: call !!0[] [System.Core]System.Linq.Enumerable::ToArray(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_0096: stloc.1
IL_0097: ldloc.1
IL_0098: call int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_009d: ldc.i4.3
IL_009e: ceq
IL_00a0: ldc.i4.0
IL_00a1: ceq
IL_00a3: call void [System]System.Diagnostics.Debug::Assert(bool)
IL_00a8: nop
IL_00a9: ret
} // end of method FrmMain::FrmMain_ShownRelease:
IL\_0089: call class \[mscorlib\]System.Collections.Generic.IEnumerable\`1 \[System.Core\]System.Linq.Enumerable::Select, string>(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1, class \[mscorlib\]System.Func\`2) IL\_008e: call !!0\[\] \[System.Core\]System.Linq.Enumerable::ToArray(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1) IL\_0093: pop IL\_0094: ret
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
-
I just checked, and Debug.Assert is completely removed from Release code: Code:
private void FrmMain\_Shown(object sender, EventArgs e) { ... string\[\] dataWithCounts = dataFromTextBox.GroupBy(d => d).Select(g =>$"{g.Key}({g.Count()})").ToArray(); Debug.Assert(dataWithCounts.Count() != 3); }
Debug:
IL_008c: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2)
IL_0091: call !!0[] [System.Core]System.Linq.Enumerable::ToArray(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_0096: stloc.1
IL_0097: ldloc.1
IL_0098: call int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_009d: ldc.i4.3
IL_009e: ceq
IL_00a0: ldc.i4.0
IL_00a1: ceq
IL_00a3: call void [System]System.Diagnostics.Debug::Assert(bool)
IL_00a8: nop
IL_00a9: ret
} // end of method FrmMain::FrmMain_ShownRelease:
IL\_0089: call class \[mscorlib\]System.Collections.Generic.IEnumerable\`1 \[System.Core\]System.Linq.Enumerable::Select, string>(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1, class \[mscorlib\]System.Func\`2) IL\_008e: call !!0\[\] \[System.Core\]System.Linq.Enumerable::ToArray(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1) IL\_0093: pop IL\_0094: ret
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
That's interesting. I just tried a similar thing with dotPeek, and the assertion is still visible in the release code, though your explanation above seems more plausible. Thanks for the link also - it backs up what you have above. I'm wondering if there is some other optimization switch in play here which explains why I can still see it...
Regards, Rob Philpott.
-
I just checked, and Debug.Assert is completely removed from Release code: Code:
private void FrmMain\_Shown(object sender, EventArgs e) { ... string\[\] dataWithCounts = dataFromTextBox.GroupBy(d => d).Select(g =>$"{g.Key}({g.Count()})").ToArray(); Debug.Assert(dataWithCounts.Count() != 3); }
Debug:
IL_008c: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select, string>(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2)
IL_0091: call !!0[] [System.Core]System.Linq.Enumerable::ToArray(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_0096: stloc.1
IL_0097: ldloc.1
IL_0098: call int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_009d: ldc.i4.3
IL_009e: ceq
IL_00a0: ldc.i4.0
IL_00a1: ceq
IL_00a3: call void [System]System.Diagnostics.Debug::Assert(bool)
IL_00a8: nop
IL_00a9: ret
} // end of method FrmMain::FrmMain_ShownRelease:
IL\_0089: call class \[mscorlib\]System.Collections.Generic.IEnumerable\`1 \[System.Core\]System.Linq.Enumerable::Select, string>(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1, class \[mscorlib\]System.Func\`2) IL\_008e: call !!0\[\] \[System.Core\]System.Linq.Enumerable::ToArray(class \[mscorlib\]System.Collections.Generic.IEnumerable\`1) IL\_0093: pop IL\_0094: ret
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
Ok explained. dotPeek will use the actual source when its available rather than disassemble. Indeed the assertion never makes into MSIL. I'm learning all sorts of new things today. :)
Regards, Rob Philpott.
-
That's interesting. I just tried a similar thing with dotPeek, and the assertion is still visible in the release code, though your explanation above seems more plausible. Thanks for the link also - it backs up what you have above. I'm wondering if there is some other optimization switch in play here which explains why I can still see it...
Regards, Rob Philpott.
Does your release build still have the
DEBUG
conditional compilation symbol defined? Reference Source[^] ConditionalAttribute Class (System.Diagnostics) | Microsoft Docs[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Does your release build still have the
DEBUG
conditional compilation symbol defined? Reference Source[^] ConditionalAttribute Class (System.Diagnostics) | Microsoft Docs[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Nope, good thought though. TRACE is defined. Explanation is below, dotPeek uses the actual source rather than reconstituted when available, so it was showing even for the release build.
Regards, Rob Philpott.
-
Ok explained. dotPeek will use the actual source when its available rather than disassemble. Indeed the assertion never makes into MSIL. I'm learning all sorts of new things today. :)
Regards, Rob Philpott.
Yeah, I like dotPeek, but that "use the source" bit has thrown me a couple of times. ILSpy is a bit more basic, but sometimes that's exactly what you want.
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
-
Yeah, I like dotPeek, but that "use the source" bit has thrown me a couple of times. ILSpy is a bit more basic, but sometimes that's exactly what you want.
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
Quote:
a bit more basic, but sometimes that's exactly what you want.
We have a special forum for that :doh:
Luc Pattyn [My Articles] Nil Volentibus Arduum
-
Quote:
a bit more basic, but sometimes that's exactly what you want.
We have a special forum for that :doh:
Luc Pattyn [My Articles] Nil Volentibus Arduum
:laugh: Nobody really wants VB!
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony AntiTwitter: @DalekDave is now a follower!
-
I'm writing some low level C# which is doing bit level operations (shifting/logic) which needs to be very high performance (real time application). Usually, I'd just do some checks and throw an exception if things aren't right, but I can't afford the overhead of that here. Back in the day, in C++ I'd just use assertions for my checks which would cease to exist completely in the release build. In C#, we have Debug.Assert but its use seems greyer to me, because debug vs. release in .NET is greyer. Macros disappear depending on switches, but it seems the only way a Debug.Assert can disappear is if its hardedcoded in the JIT that way. So my question I guess is does the Debug.Assert make it into the MSIL regardless of debug/release, and then does the JIT just remove it for release and remove it completely? What happens with something like this in a release build, does the counter increment?
Debug.Assert(++counter != 100);
Regards, Rob Philpott.