Рядом постоянное время вращается, что не нарушает стандарты

У меня есть чертовски время, пытаясь придумать постоянное rotation времени, которое не нарушает стандарты C / C ++.

Проблема заключается в случаях, когда в алгоритмах вызывают операции, и эти алгоритмы не могут быть изменены. Например, из Crypto ++ и выполняется тестовый жгут в GCC ubsan (т. g++ fsanitize=undefined ):

 $ ./cryptest.exe v | grep runtime misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' misc.h:625:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int' 

И код на misc.h:637 :

 template  inline T rotlMod(T x, unsigned int y) { y %= sizeof(T)*8; return T((x<>(sizeof(T)*8-y))); } 

Intel ICC был особенно беспощаден, и он удалил весь вызов функции с помощью y %= sizeof(T)*8 . Мы исправили это несколько лет назад, но оставили другие ошибки на месте из-за отсутствия постоянного решения времени.

Остается одна боль. Когда y = 0 , я получаю условие, когда 32 - y = 32 , и устанавливает неопределенное поведение. Если я добавлю чек для if(y == 0) ... , тогда код не сможет удовлетворить требование постоянного времени.

Я рассмотрел ряд других реализаций: от ядра Linux до других криптографических библиотек. Все они содержат одно и то же неопределенное поведение, поэтому он кажется тупиковым.

Как я могу выполнить поворот почти в постоянное время с минимальным количеством инструкций?

EDIT : почти постоянное время , я имею в виду, избегаю ветви, так что всегда выполняются одни и те же инструкции. Я не беспокоюсь о таймингах микрокода процессора. Хотя предсказание ветвления может быть велико на x86 / x64, оно может не работать также на других платформах, например встраиваемых.


Ни один из этих трюков не потребовался бы, если бы GCC или Clang обеспечивали внутреннее выполнение вращения почти в постоянное время . Я даже согласился на «выполнить поворот», так как у них даже нет этого.

Я связался с этим ответом для получения полной информации из нескольких других вопросов «повернуть», включая этот вопрос о вики сообщества , который должен быть обновлен с использованием лучших практик.

Я нашел сообщение в блоге об этой проблеме, и похоже, что это окончательно решена проблема (с новыми версиями компилятора).

Джон Регер в Университете штата Юта рекомендует версию «c» своих попыток сделать функцию вращения. Я заменил его assert поразрядным AND и обнаружил, что он все еще компилируется в один rotate insn.

 typedef uint32_t rotwidth_t; // parameterize for comparing compiler output with various sizes rotwidth_t rotl (rotwidth_t x, unsigned int n) { const unsigned int mask = (CHAR_BIT*sizeof(x)-1); // eg 31 assert ( (n<=mask) &&"rotate by type width or more"); n &= mask; // avoid undef behaviour with NDEBUG. 0 overhead for most types / compilers return (x<>( (-n)&mask )); } rotwidth_t rot_const(rotwidth_t x) { return rotl(x, 7); } 

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

Также обязательно используйте это только для неподписанных типов. Правый сдвиг подписанных типов выполняет арифметический сдвиг, сдвигая знаковые биты. (Это технически зависит от реализации, но теперь все использует дополнение 2).

Пабигот самостоятельно придумал ту же идею до того, как я это сделал, и разместил ее в gibhub . Его версия имеет проверку C ++ static_assert, чтобы сделать ошибку времени компиляции для использования числа вращения за пределами диапазона для типа.

Я проверил мой с gcc.godbolt.org , с определенным NDEBUG, для переменных и компиляции-const-count rotate counts:

  • gcc: оптимальный код с gcc> = 4.9.0 , не ветвящийся neg + сдвиг + или с более ранним.
    (время подсчета времени компиляции: gcc 4.4.7 в порядке)
  • clang: оптимальный код с clang> = 3.5.0 , не ветвящийся neg + сдвиг + или с более ранним.
    (compile-time const rotate count: clang 3.0 отлично)
  • icc 13: оптимальный код.
    (время подсчета compile-time с -march = native: генерирует более медленный shld $7, %edi, %edi . Fine без -march=native )

Даже более новые версии компилятора могут обрабатывать общепринятый код из википедии (включенной в образец godbolt) без создания ветки или cmov. Преимущество Джона Реджера состоит в том, что можно избежать неопределенного поведения, когда число оборотов равно 0.

Есть некоторые оговорки с 8 и 16 бит вращается, но компиляторы кажутся точными с 32 или 64, когда nuint32_t . См. Комментарии в коде на ссылке godbolt для некоторых заметок от моего тестирования различной ширины uint*_t . Надеемся, что эта идиома будет лучше распознана всеми компиляторами для большего количества комбинаций ширины типов в будущем. Иногда gcc бесполезно испускает AND insn на счетчик вращения, хотя x86 ISA определяет rotation insns с этим точным AND как первый шаг.

«оптимальный» означает такую ​​же эффективную, как:

 # gcc 4.9.2 rotl(unsigned int, unsigned int): movl %edi, %eax movl %esi, %ecx roll %cl, %eax ret # rot_const(unsigned int): movl %edi, %eax roll $7, %eax ret 

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

С более старыми компиляторами вы все равно получите идеальный код, когда счетчик вращения будет константой времени компиляции. Godbolt позволяет вам тестировать с помощью ARM в качестве цели, и он также использовал поворот. С переменными счетчиками на старых компиляторах вы получаете немного раздувания кода, но никаких ветвей или серьезных проблем с производительностью, поэтому эта идиома должна быть безопасной для использования в целом.

BTW, я модифицировал оригинал Джона Реджера, чтобы использовать CHAR_BIT * sizeof (x), а gcc / clang / icc испускает оптимальный код для uint64_t . Тем не менее, я заметил, что смена x на uint64_t то время как возвращаемый тип функции все еще uint32_t делает gcc скомпилировать его для сдвигов / или. Поэтому будьте осторожны, чтобы привести результат к 32 бит в отдельной точке последовательности, если вы хотите, чтобы низкий 32b 64b вращался. т.е. присваивать результат 64-битной переменной, а затем приводить / возвращать ее. icc по-прежнему генерирует rotate insn, но gcc и clang этого не делают, поскольку

 // generates slow code: cast separately. uint32_t r = (uint32_t)( (x<>( -n&(CHAR_BIT*sizeof(x)-1) )) ); 

Если кто-нибудь сможет проверить это с помощью MSVC, было бы полезно узнать, что там происходит.

Вы можете добавить одну дополнительную операцию modulo, чтобы предотвратить смещение на 32 бита, но я не уверен, что это быстрее, чем использование проверки if в сочетании с предсказателями ветвления.

 template  inline T rotlMod(T x, unsigned int y) { y %= sizeof(T)*8; return T((x<>((sizeof(T)*8-y) % (sizeof(T)*8)))); } 

Написание выражения в виде T((x<>(sizeof(T)*CHAR_BITS-y-1)>>1)) должно давать определенное поведение для всех значений y ниже размера бита, предполагая что T является неподписанным типом без заполнения. Если у компилятора нет хорошего оптимизатора, полученный код может быть не таким хорошим, как то, что было бы создано вашим оригинальным выражением. Должно быть, чтобы смириться с неудобным для чтения кодом, который даст Тем не менее, медленное выполнение многих компиляторов является частью цены прогресса, поскольку гиперкомпьютерный компилятор, который задан

 if (y) do_something(); return T((x<>(sizeof(T)*8-y))); 

может улучшить «эффективность» кода, сделав вызов do_something безусловным.

PS: Интересно, существуют ли какие-либо платформы реального мира, где изменение определения shift-right так, что x >> y когда y точно равно размеру бита x , потребовалось бы либо 0, либо x, но могло бы сделать выбор произвольным (неуказанным) способом, потребовал бы, чтобы платформа генерировала дополнительный код или исключала бы действительно полезные оптимизации в ненастроенных сценариях?

Альтернативой дополнительному модулю является умножение на 0 или 1 (спасибо !! ):

 template  T rotlMod(T x, unsigned int y) { y %= sizeof(T) * 8; return T((x << y) | (x >> ((!!y) * (sizeof(T) * 8 - y))); } 
Interesting Posts

Что такое имя массива в c?

Сопоставление всех слов, кроме одного

Когда Fragment заменяется и помещается в задний стек (или удаляется), он остается в памяти?

Как написать базу данных (2) в c / c ++

Как работать с временными экземплярами NSManagedObject?

Можно ли экспортировать GPO с одного сервера и импортировать в другой?

Как передать пользовательскую переменную окружения на Amazon Elastic Beanstalk (AWS EBS)?

Изменение значка меню «Переполнение Android» программно

Скрытие «инструментов», «комментариев» и «значков» в Adobe Reader XI

Как настроить машину Linux для использования детьми в Интернете?

Спящий / спящий режим Подтверждение экрана спящего / спящего режима?

как определить специальные символы в массиве, реализующем java-приложение?

POST запрос отправить json данные java HttpUrlConnection

Странное предупреждение компилятора C: предупреждение: «struct» объявлен в списке параметров

Вызов конструктора базового classа из конструктора производного classа

Давайте будем гением компьютера.