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);
И теперь вы увидите, что больше нет разницы.