Typedef для индексов в C# со статической проверкой типов без затрат времени выполнения

Довольно распространенным случаем является использование многомерных массивов со сложной индексацией. Это действительно сбивает с толку и подвержено ошибкам, когда все индексы являются целочисленными, потому что вы можете легко смешивать столбцы и строки (или все, что у вас есть), и у компилятора нет способа определить проблему. Фактически должно быть два типа индексов: строки и столбцы, но это не выражено на уровне типа.

Вот небольшая иллюстрация того, что я хочу:

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

Подводя итог:

  • индексы должны быть статически проверены на смешение (тип проверки типа)
  • важный! они должны быть эффективными во время выполнения, так как это не нормально для производительности - оборачивать целые объекты в пользовательские объекты, содержащие индекс, а затем разворачивать их обратно
  • они должны быть неявно конвертируемыми в int, чтобы служить индексами в собственных многомерных массивах

Есть ли способ добиться этого? Идеальное решение было бы что-то вроде typedef который служит проверкой во время компиляции только при компиляции в плоские целые.

1 ответ

Вы получите только 2-кратное замедление с джиттером x64. Он генерирует интересный оптимизированный код. Цикл, который использует структуру, выглядит следующим образом:

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 

Это требует некоторой аннотации, так как код необычен. Во-первых, странный NOP со смещением 45 предназначен для выравнивания инструкции в начале цикла. Это делает ветку со смещением 64 быстрее. Инструкция на 53 выглядит совершенно ненужной. То, что вы видите здесь, - это развертывание цикла, обратите внимание, что инструкция на 5b увеличивает счетчик цикла на 2. Однако оптимизатор не настолько умен, чтобы потом также увидеть, что хранилище не нужно.

И больше всего обратите внимание, что нет инструкции ADD, которую можно увидеть. Другими словами, код фактически не вычисляет значение "суммы". Это потому, что вы не используете его нигде после цикла, оптимизатор может увидеть, что вычисления бесполезны, и полностью удалил его.

Это делает намного лучшую работу во втором цикле:

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

Теперь он полностью удалил вычисление "суммы" и присвоение переменной "i". Он также мог бы удалить весь цикл for(), но оптимизатор дрожания этого никогда не делает, он предполагает, что задержка является преднамеренной.

Надеюсь, к этому моменту все ясно: избегайте предположений на основе искусственных тестов и только когда-либо профилируйте реальный код. Вы можете сделать это более реальным, фактически отобразив значение "сумма", чтобы оптимизатор не отбрасывал вычисления. Добавьте эту строку кода после цикла:

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

И теперь вы увидите, что больше нет разницы.

Другие вопросы по тегам