Я написал симулятор троичной логики не так давно, и этот вопрос был для меня жизнеспособным, поскольку он напрямую влияет на скорость выполнения интерпретатора; От меня требовалось моделировать тонны и тонны троичных логических элементов как можно быстрее.
В двоично-троичной системе один трит упакован в два бита. Наиболее значимый бит означает отрицательный, а наименее значимый - положительный. Случай "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
инструкция. К сожалению, кэши маленькие, и (если длина вашего индекса превышает несколько бит), вы просто не сможете его использовать.
Так что я думаю, что ответил на ваш вопрос о том, на что может быть похож разветвленный / безветвленный код. Ответ гораздо лучше и с подробными образцами, реальным применением и результатами измерений реальных характеристик.