OMG только что завернул.
Чего не хватает большинству этих примеров, так это небольшого понимания того, как работает все оборудование.
Каждый раз, когда у вас есть ветка, процессор должен угадать, какая ветвь будет взята. Канал инструкций загружается с инструкциями, которые ведут по предполагаемому пути. Если ЦП ошибся, значит канал инструкций сбрасывается, а другая ветвь должна быть загружена.
Рассмотрим простой цикл while сверху. Предположение будет оставаться в цикле. Это будет неправильно по крайней мере один раз, когда он выйдет из цикла. Это промоет канал инструкций. Это поведение немного лучше, чем догадываться, что он выйдет из цикла, и в этом случае он будет очищать канал инструкций на каждой итерации.
Количество потерянных циклов ЦП сильно варьируется от одного типа процессора к другому. Но вы можете ожидать от 20 до 150 потерянных циклов ЦП.
Следующая худшая группа, где вы думаете, что вы собираетесь сэкономить несколько итераций, разделив значение на более мелкие части и добавив еще несколько ветвей. Каждая из этих веток добавляет дополнительную возможность очистить канал инструкций и стоит еще от 20 до 150 тактов.
Давайте рассмотрим, что происходит, когда вы ищите значение в таблице. Скорее всего, значение в данный момент отсутствует в кэше, по крайней мере, не в первый раз, когда вызывается ваша функция. Это означает, что процессор останавливается, пока значение загружается из кеша. Опять же, это зависит от одной машины к другой. Новые чипы Intel фактически используют это как возможность для обмена потоками, пока текущий поток ожидает завершения загрузки кэша. Это легко может быть дороже, чем очистка канала инструкций, однако, если вы выполняете эту операцию несколько раз, она может произойти только один раз.
Очевидно, что самым быстрым решением с постоянным временем является решение, которое включает в себя детерминистическую математику. Чистое и элегантное решение.
Мои извинения, если это уже было покрыто.
Каждый компилятор, который я использую, кроме XCODE AFAIK, имеет встроенные функции компилятора как для прямого, так и для обратного битового сканирования. Они будут скомпилированы в одну инструкцию по сборке на большинстве аппаратных средств без пропусков Cache, Miss-Prediction и других программистов, генерирующих камни преткновения.
Для компиляторов Microsoft используйте _BitScanForward & _BitScanReverse.
Для GCC используйте __builtin_ffs, __builtin_clz, __builtin_ctz.
Кроме того, пожалуйста, воздержитесь от публикации ответа и, возможно, вводите в заблуждение новичков, если вы недостаточно осведомлены о обсуждаемом предмете.
Извините, я совершенно забыл предоставить решение. Это код, который я использую на IPAD, в котором нет инструкции уровня сборки для этой задачи:
unsigned BitScanLow_BranchFree(unsigned value)
{
bool bwl = (value & 0x0000ffff) == 0;
unsigned I1 = (bwl * 15);
value = (value >> I1) & 0x0000ffff;
bool bbl = (value & 0x00ff00ff) == 0;
unsigned I2 = (bbl * 7);
value = (value >> I2) & 0x00ff00ff;
bool bnl = (value & 0x0f0f0f0f) == 0;
unsigned I3 = (bnl * 3);
value = (value >> I3) & 0x0f0f0f0f;
bool bsl = (value & 0x33333333) == 0;
unsigned I4 = (bsl * 1);
value = (value >> I4) & 0x33333333;
unsigned result = value + I1 + I2 + I3 + I4 - 1;
return result;
}
Здесь нужно понять, что дороже не сравнение, а ответвление, которое происходит после сравнения. Сравнение в этом случае принудительно устанавливается на значение 0 или 1 с .. == 0, а результат используется для объединения математических вычислений, которые произошли бы по обе стороны ветви.
Edit:
Код выше полностью не работает. Этот код работает и по-прежнему без веток (если оптимизирован):
int BitScanLow_BranchFree(ui value)
{
int i16 = !(value & 0xffff) << 4;
value >>= i16;
int i8 = !(value & 0xff) << 3;
value >>= i8;
int i4 = !(value & 0xf) << 2;
value >>= i4;
int i2 = !(value & 0x3) << 1;
value >>= i2;
int i1 = !(value & 0x1);
int i0 = (value >> i1) & 1? 0 : -32;
return i16 + i8 + i4 + i2 + i1 + i0;
}
Возвращает -1, если задано 0. Если вас не волнует 0 или вы хотите получить 31 за 0, удалите вычисление i0, сэкономив часть времени.