Почему создаются инструкции AND? - PullRequest
34 голосов
/ 28 марта 2012

Для такого кода:

int res = 0;
for (int i = 0; i < 32; i++)
{
    res += 1 << i;
}

Этот код генерируется (режим выпуска, отладчик не подключен, 64 бита):

 xor edx,edx 
 mov r8d,1 
_loop:
 lea ecx,[r8-1] 
 and ecx,1Fh     ; why?
 mov eax,1 
 shl eax,cl 
 add edx,eax 
 mov ecx,r8d 
 and ecx,1Fh     ; why?
 mov eax,1 
 shl eax,cl 
 add edx,eax 
 lea ecx,[r8+1] 
 and ecx,1Fh     ; why?
 mov eax,1 
 shl eax,cl 
 add edx,eax 
 lea ecx,[r8+2] 
 and ecx,1Fh     ; why?
 mov eax,1 
 shl eax,cl 
 add edx,eax 
 add r8d,4 
 cmp r8d,21h 
 jl  _loop

Теперь я вижу смысл большинства инструкций, но что там с инструкциями AND? ecx никогда не будет больше 0x1F в этом коде в любом случае, но я извиняюсь за то, что не заметил этого (а также за то, что не заметил, что результат является константой), это не опережающий компилятор, который может позволить себе тратить много времени на анализ в конце концов. Но что еще более важно, SHL с 32-битным операндом маскирует уже значение 0x1F. Поэтому мне кажется, что эти AND совершенно бесполезны. Почему они генерируются? У них есть какая-то цель, по которой я скучаю?

Ответы [ 3 ]

27 голосов
/ 28 марта 2012

and уже присутствует в коде CIL, генерируемом компилятором C #:

    IL_0009: ldc.i4.s 31
    IL_000b: and
    IL_000c: shl

В спецификации для инструкции CIL shl указано:

возвращаемое значение не указывается, если shiftAmount больше или равно размеру value .

Спецификация C #, однако, определяет 32-битный сдвигчтобы принять моду счет сдвига 32:

Когда тип x равен int или uint,, счет сдвига задается младшими пятью битами счета,Другими словами, число сдвигов вычисляется из count & 0x1F.

. В этой ситуации компилятор C # не может добиться гораздо больших результатов, чем явная операция and.Лучшее, на что вы можете надеяться, это то, что JITter заметит это и оптимизирует избыточный and, но это требует времени, и скорость JIT очень важна.Поэтому рассмотрим эту цену, заплаченную за систему на основе JIT.

Реальный вопрос, я думаю, заключается в том, почему CIL задает инструкцию shl таким образом, когда C # и x86 оба задают поведение усечения.Этого я не знаю, но я полагаю, что для спецификации CIL важно избегать указания поведения, которое может привести к JIT к чему-то дорогому в некоторых наборах команд.В то же время для C # важно иметь как можно меньше неопределенных поведений, потому что люди неизменно заканчивают тем, что используют такие неопределенные поведения, пока следующая версия компилятора / фреймворка / OS / что-либо еще не изменит их, нарушая код.

10 голосов
/ 28 марта 2012

x64 ядра уже применяют 5-битную маску к величине сдвига.Из руководства по процессору Intel, том 2B, стр. 4-362:

Операндом-адресатом может быть регистр или ячейка памяти.Операндом count может быть непосредственное значение или регистр CL. Счетчик маскируется до 5 бит (или 6 бит, если в 64-битном режиме используется REG.W).Специальное кодирование кода операции предусмотрено для счетчика 1.

Так что это машинный код, который не нужен.К сожалению, компилятор C # не может делать какие-либо предположения о поведении процессора и должен применять правила языка C #.И сгенерируйте IL, поведение которого указано в спецификации CLI.Ecma-335, Часть III, глава 3.58, говорит об операционном коде SHL:

Инструкция shl сдвигает значение (int32, int64 или native int) на количество бит, указанное в shiftAmount.shiftAmount имеет тип int32 или собственный int. Возвращаемое значение не указывается, если shiftAmount больше или равно ширине значения .

Не указано - это проблема.Выделение указанного поведения поверх неуказанных деталей реализации приводит к созданию ненужного кода.Технически джиттер может оптимизировать код операции.Хотя это сложно, оно не знает правила языка.Любому языку, который не указывает маскирование, будет трудно генерировать правильный IL.Вы можете отправить сообщение на connect.microsoft.com, чтобы узнать мнение команды по джиттеру.

5 голосов
/ 28 марта 2012

Компилятор C # должен вставлять эти инструкции AND при генерации промежуточного (машинно-независимого) кода, поскольку оператор сдвига влево C # должен использовать только 5 младших значащих битов.

При создании кода x86 оптимизирующий компилятор может отбросить эти ненужные инструкции. Но, по-видимому, он пропускает эту оптимизацию (возможно, потому что не может позволить себе тратить много времени на анализ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...