Does using unchecked context hurt performance or portability in C#?
-
27-10-2019 - |
Question
I want to implement a fast hash function that will use int
datatype and rely on integer overflow. MSDN says that in order to guarantee that overflows don't trigger exceptions I have to use unchecked
blocks for that code.
Suppose I surround only that computation in an unchecked
block. Will my code have any performance or portability problems because of that?
Solution
Technically only the checked
blocks should slow. So I don't think an unchecked
block (a block where the framework has to do less checks) could slow. It isn't a context switch or something similar. The JIT simply doesn't emit instructions to check for overflow/underflow. Now, clearly if someone created a "special" processor where overflow must be simulated and ported Mono on it, or where an overflow causes different results than on Intel processors, an unchecked
block would be slower (because the JIT would have to "simulate" it). But note that the base types of .NET are defined in the ECMA standard. Signed ints must be based on two-complement for example, and their size must be 8, 16, 32, 64 bits. There isn't much space for "strange" processors that use 36 bits integers.
OTHER TIPS
checked add only one process instruction:
checked
{
int y = x * x;
05297978 mov eax,dword ptr [ebp-10h]
0529797B imul eax,dword ptr [ebp-10h]
0529797F jno 05297986 //if not overflow: goto 05297986
05297981 call 72A29522 //invoke exception
05297986 mov dword ptr [ebp-14h],eax
}
unchecked
{
int yy = xx * xx;
0529799E mov eax,dword ptr [ebp-18h]
052979A1 imul eax,dword ptr [ebp-18h]
052979A5 mov dword ptr [ebp-1Ch],eax
}
As a rule you can expect unchecked arithmetic to be slightly faster, but it's pretty much never worth asking the opposite question to yours (viz. "will using checked hurt my performance?").
Checked and unchecked just mean there are slightly different rules as to how operators like +
, *
, and -
are treated, and you should use the one appropriate for the case in hand.
In this case, you quite definitely want unchecked, so you should state this in your code. This actually increases portability, since you will then have the same behaviour whatever compiler switches are used with it.
I've created two methods, one wrapped by checked
and an other by unchecked
. By looking into IL only one difference is mul
operation (which does multiplication operation), for checked mul.ovf
is generated and for unchecked - mul
.
To summarize, I believe difference in a single CPU operation does not make any impact on performance, the only one difference would be in case of overflow using checked
- in this case OverflowException will be generated which obviously slow down execution.
The following Microsoft intermediate language (MSIL) instructions throw an OverflowException:
- mul.ovf.
- ...
[Test]
public void Checked()
{
checked
{
int i = int.MaxValue;
i = i * 100;
Debug.WriteLine(i);
}
}
[Test]
public void UnChecked()
{
unchecked
{
int i = int.MaxValue;
i = i * 100;
Debug.WriteLine(i);
}
}
And then using ILDASM see IL:
CHECKED():
// Code size 27 (0x1b)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4 0x7fffffff
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.s 100
**IL_000b: mul.ovf** !!!
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: box [mscorlib]System.Int32
IL_0013: call void [System]System.Diagnostics.Debug::WriteLine ...
UNCHECKED():
// Code size 27 (0x1b)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: nop
IL_0001: nop
IL_0002: ldc.i4 0x7fffffff
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.s 100
**IL_000b: mul** !!!
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: box [mscorlib]System.Int32
IL_0013: call void [System]System.Diagnostics.Debug::WriteLine(...)