MSVC генерирует странный/медленный двоичный код для некоторых умножений и делений

Я использую MSVC 2010 SP1, и у меня есть следующая строка кода C++:

int32_t c = (int64_t(a)*int64_t(b))>>2;

Когда a и b не являются константами, MSVC правильно генерирует 32-битные инструкции imul и shrd. Но когда a или b являются константами, генерируется вызов _allmull вместо инструкции imul. Может ли быть какая-то причина для этого? Как я могу заставить его всегда генерировать хороший код? Меня беспокоит то, почему он генерирует худший код, когда у него больше информации о времени компиляции. Я обнаружил, что функция _allmull выполняет 64-битное умножение, но я думаю, что в данном случае это не нужно.

Я также заметил, что для строки int32_t c = (int64_t(a)*int64_t(b))/4; он даже генерирует _alldiv для деления на 4.

Изменить: похоже, это ошибка компилятора. Я заполнил ошибку отчет.


person Juraj Blaho    schedule 06.04.2011    source источник
comment
Почему вы используете int64_t, если знаете, что он не нужен?   -  person Erik    schedule 06.04.2011
comment
Семантика целых чисел со знаком и без знака различается. Что делать, если вы используете uint32_t и uint64_t?   -  person Alexandre C.    schedule 06.04.2011
comment
@Erik: На процессорах, совместимых с Intel (и, вероятно, на большинстве других) imul с двумя аргументами 32b генерирует результат 64b. И мне нужно только сдвинуть этот результат, прежде чем он будет назначен 32-битной переменной. 64-битное умножение не требуется.   -  person Juraj Blaho    schedule 06.04.2011
comment
@Alexandre C.: Для значений без знака сгенерированная сборка верна во всех трех упомянутых случаях. Но мне нужно, чтобы он работал для подписанных значений. Какие-нибудь советы?   -  person Juraj Blaho    schedule 06.04.2011
comment
@Juraj: нет, извини. Я подозревал эту проблему, но я не специалист в этой области. По крайней мере, вы знаете, в чем ваша проблема.   -  person Alexandre C.    schedule 06.04.2011
comment
Вы должны предоставить более полный раздел кода с отчетом об ошибке. MS воспользуется любой возможностью, чтобы закрыть ошибки в Connect, и отсутствие компилируемого кода или используемых вами параметров компилятора сделает это еще более вероятным.   -  person Will Dean    schedule 06.04.2011


Ответы (3)


Частично связано: если вы хотите использовать imul возможность выполнения 32x32=›64-битного умножения, вы можете использовать Int32x32To64 поддельный API (на самом деле макрос):

Умножает два 32-битных целых числа со знаком, возвращая 64-битное целое число со знаком. Функция оптимально работает в 32-разрядной версии Windows.

Эта функция реализована на всех платформах оптимальным встроенным кодом: одна инструкция умножения, которая возвращает 64-битный результат.

Кстати, вы включили оптимизацию? Я был бы весьма сбит с толку, если бы с включенной оптимизацией компилятор не смог понять это сам.


Изменить:

что интересно, ища Int32x32To64 в winnt.h, находим для x86:

//
// The x86 C compiler understands inline assembler. Therefore, inline functions
// that employ inline assembler are used for shifts of 0..31.  The multiplies
// rely on the compiler recognizing the cast of the multiplicand to int64 to
// generate the optimal code inline.
//

#define Int32x32To64( a, b ) (LONGLONG)((LONGLONG)(LONG)(a) * (LONG)(b))
#define UInt32x32To64( a, b ) (ULONGLONG)((ULONGLONG)(DWORD)(a) * (DWORD)(b))

Таким образом, он определенно должен выдавать imul, если даже Platform SDK доверяет компилятору делать правильные вещи.


Изменить еще раз:

Если вам нужно получить imul, вы можете использовать __emul встроенный компилятор.

person Matteo Italia    schedule 06.04.2011
comment
В моем случае этот макрос тоже не работает. Без операции >> это работает прекрасно. Но сдвиг каким-то образом вводит компилятор в заблуждение, чтобы сгенерировать call _allmul - person Juraj Blaho; 06.04.2011
comment
@Juraj: как насчет встроенного __emul? - person Matteo Italia; 06.04.2011
comment
Я только что попробовал __emul, и, кажется, он работает. Мне это не очень нравится, потому что это очень специфично для компилятора и непереносимо, но я мог бы использовать его как обходной путь, когда используется MSVC. Спасибо. - person Juraj Blaho; 06.04.2011
comment
@Juraj: мне тоже не нравятся вещи, специфичные для компилятора, но я не вижу много других вариантов; с другой стороны, было бы интересно сравнить код, сгенерированный со встроенной функцией и без нее, было бы приятным сюрпризом, если бы оказалось, что компилятор на самом деле знает, что он делает, и, казалось бы, более медленный код в этом случае на самом деле быстрее. - person Matteo Italia; 06.04.2011
comment
@Matteo Italia: Компилятор, похоже, здесь неправ. Согласно экспресс-тесту версия _allmull медленнее в 2 раза. Если я использую деление на 4 вместо >>2, компилятор генерирует _alldiv и код работает в 10 раз медленнее, чем мог бы быть с __emul и >>. - person Juraj Blaho; 06.04.2011
comment
@Juraj: ну, может быть, есть материал для отчета об ошибке. - person Matteo Italia; 06.04.2011

Я вижу allmul, если запускаю компилятор без оптимизации, но с /Ox я вижу комбинацию сдвигов и добавлений, зависящую от значения константной части.

Я думаю, вам нужно предоставить конкретный фрагмент кода и параметры компилятора, которые вы использовали.

person Will Dean    schedule 06.04.2011
comment
Вы пробовали какую-то большую константу, не равную степени двойки (например, 150)? Для небольших констант, похоже, выполняются некоторые дополнительные оптимизации. У меня включена оптимизация (/Ox). - person Juraj Blaho; 06.04.2011

Вы пробовали в качестве обходного пути:

int32_t c = (int64_t(int32_t(a))*int64_t(int32_t(b)))>>2;
person Mark Ransom    schedule 06.04.2011
comment
Да и вроде не помогает. Это эквивалентно Int32x32To64(), предложенному Matteo Italia. - person Juraj Blaho; 06.04.2011
comment
Привет! Приятно познакомиться на SO Meetup! - person jjnguy; 07.04.2011