Я написал симулятор троичной логики не так давно, и этот вопрос был для меня жизнеспособным, поскольку он напрямую влияет на скорость выполнения интерпретатора;От меня требовалось моделировать тонны и тонны троичных логических элементов как можно быстрее.
В двоично-кодированной троичной системе один трит упакован в два бита.Наиболее значимый бит означает отрицательный, а наименее значимый - положительный.Случай "11" не должен возникать, но он должен обрабатываться надлежащим образом и указываться как 0.
Рассмотрим функцию inline int bct_decoder( unsigned bctData )
, которая должна возвращать наш отформатированный трит как обычное целое число -1, 0 или 1;Как я заметил, есть 4 подхода: я назвал их «cond», «mod», «math» и «lut»;Давайте исследуем их
Первый основан на условных переходах jz | jnz и jl | jb, таким образом, cond.Его производительность не очень хорошая, потому что зависит от предсказателя ветвления.И что еще хуже - это меняется, потому что неизвестно, будет ли априори одна или две ветви.И вот пример:
inline int bct_decoder_cond( unsigned bctData ) {
unsigned lsB = bctData & 1;
unsigned msB = bctData >> 1;
return
( lsB == msB ) ? 0 : // most possible -> make zero fastest branch
( lsB > msB ) ? 1 : -1;
}
Это самая медленная версия, в худшем случае она может включать 2 ветви, и это то, где двоичная логика дает сбой.На моих 3770k он дает в среднем около 200 МИПС на случайных данных.(здесь и после - каждый тест является средним из 1000 попыток для случайно заполненного набора данных 2 МБ)
Далее следует оператор по модулю, и его скорость находится где-то между первым и третьим, но определенно быстрее - 600 MIPS:
inline int bct_decoder_mod( unsigned bctData ) {
return ( int )( ( bctData + 1 ) % 3 ) - 1;
}
Следующим является подход без ответвлений, который включает только математику, то есть математику;он вообще не принимает инструкции перехода:
inline int bct_decoder_math( unsigned bctData ) {
return ( int )( bctData & 1 ) - ( int )( bctData >> 1 );
}
Это делает то, что должно, и ведет себя действительно великолепно.Для сравнения, оценка производительности составляет 1000 MIPS, и это в 5 раз быстрее, чем разветвленная версия.Возможно, разветвленная версия замедлена из-за отсутствия встроенной поддержки 2-битных подписанных int.Но в моем приложении это довольно хорошая версия сама по себе.
Если этого недостаточно, мы можем пойти дальше, имея что-то особенное.Следующий метод называется таблицей поиска:
inline int bct_decoder_lut( unsigned bctData ) {
static const int decoderLUT[] = { 0, 1, -1, 0 };
return decoderLUT[ bctData & 0x3 ];
}
В моем случае один трит занимал только 2 бита, поэтому таблица lut составляла всего 2b * 4 = 8 байт и стоила попробовать.Он помещается в кэш и работает быстро со скоростью 1400-1600 MIPS, здесь моя точность измерений снижается.И это в 1,5 раза быстрее по сравнению с быстрым математическим подходом.Это потому, что у вас есть только заранее вычисленный результат и одна AND
инструкция.К сожалению, кеши малы, и (если длина вашего индекса превышает несколько бит), вы просто не сможете его использовать.
Так что я думаю, что ответил на ваш вопрос о том, на что может быть похож разветвленный / безветвленный код.Ответ гораздо лучше и с подробными образцами, реальным применением и результатами измерений реальных характеристик.