简体   繁体   中英

Typedef for indexes in C# with static type checking without runtime overhead

It's pretty common case to use multidimensional arrays with complicated indexing. It's really confusing and error-prone when all indexes are ints because you can easily mix up columns and rows (or whatever you have) and there's no way for compiler to identify the problem. In fact there should be two types of indexes: rows and columns but it's not expressed on type level.

Here's a small illustration of what I want:

var table = new int[RowsCount,ColumnsCount];
Row row = 5;
Column col = 10;
int value = table[row, col];

public void CalcSum(int[,] table, Column col)
{
    int sum = 0;
    for (Row r = 0; r < table.GetLength(0); r++)
    {
        sum += table[row, col];
    }
    return sum;
}

CalcSum(table, col); // OK
CalcSum(table, row); // Compile time error

Summing up:

  • indexes should be statically checked for mixing up (kind of type check)
  • important! they should be run time efficient since it's not OK for performance to wrap ints to custom objects containing the index and then unwrapping them back
  • they should be implicitly convertible to ints in order to serve as indexes in native multidimensional arrays

Is there any way to achieve this? The perfect solution would be something like typedef which serves as compile-time check only compiling into plane ints.

You'll only get a 2x slowdown with the x64 jitter. It generates interesting optimized code. The loop that uses the struct looks like this:

00000040  mov         ecx,1 
00000045  nop         word ptr [rax+rax+00000000h] 
00000050  lea         eax,[rcx-1] 
                s.Idx = j;
00000053  mov         dword ptr [rsp+30h],eax 
00000057  mov         dword ptr [rsp+30h],ecx 
0000005b  add         ecx,2 
            for (int j = 0; j < 100000000; j++) {
0000005e  cmp         ecx,5F5E101h 
00000064  jl          0000000000000050 

This requires some annotation since the code is unusual. First off, the weird NOP at offset 45 is there to align the instruction at the start of the loop. That makes the branch at offset 64 faster. The instruction at 53 looks completely unnecessary. What you see happen here is loop unrolling , note how the instruction at 5b increments the loop counter by 2. The optimizer is however not smart enough to then also see that the store is unnecessary.

And most of all note that there's no ADD instruction to be seen. In other words, the code doesn't actually calculate the value of "sum". Which is because you are not using it anywhere after the loop, the optimizer can see that the calculation is useless and removed it entirely.

It does a much better job at the second loop:

000000af  xor         eax,eax 
000000b1  add         eax,4 
            for (int j = 0; j < 100000000; j++) {
000000b4  cmp         eax,5F5E100h 
000000b9  jl          00000000000000B1 

It now entirely removed the "sum" calculation and the "i" variable assignment. It could have also removed the entire for() loop but that's never done by the jitter optimizer, it assumes that the delay is intentional.

Hopefully the message is clear by now: avoid making assumptions from artificial benchmarks and only ever profile real code. You can make it more real by actually displaying the value of "sum" so the optimizer doesn't throw away the calculation. Add this line of code after the loops:

        Console.Write("Sum = {0} ", sum);

And you'll now see that there's no difference anymore.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM