Ваше слово описание процесса является правильным, но иллюстрация вашего псевдокода неточная и неполная.
Вам необходимо скопировать младший бит counter
, прежде чем очистить его;иначе вы потеряете биты, которые нужно поменять местами.Вам необходимо правильно очистить LSB, и вы можете обратить биты LSB обратно обратно в счетчик LSB следующим образом:
// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;
// Clear counter LSB
counter &= 0xff00u ;
// Reverse LSB bits and mask into counter LSB
for( uint8_t mask = 0x80u;
mask != 0;
lsb >>= 1, mask >>= 1 )
{
counter |= ((lsb & 0x01u) != 0) ? mask : 0 ;
}
Вам также следует использовать типы stdint.h uint16_t
и uint8_t
дляЭта операция, вместо того, чтобы полагаться на int
, имеет какой-либо определенный размер - она сделает код более переносимым и тестируемым в системе, где int
не является 16-битным.И, как правило, при выполнении побитовых операций следует использовать типы без знака.
Несколько более быстрый метод, хотя, возможно, требующий немного больше места в ПЗУ, - это использование справочной таблицы.256-байтовая таблица поиска довольно громоздка для генерации и на ATtiny довольно непомерно с точки зрения использования памяти.Скорее, это можно сделать почти так же эффективно, используя 16-байтовый поиск следующим образом:
// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;
// Clear counter LSB
counter &= 0xff00u ;
static const uint8_t lookup[] = { 0x0, 0x8, 0x4, 0xC,
0x2, 0xA, 0x6, 0xE,
0x1, 0x9, 0x5, 0xD,
0x3, 0xB, 0x7, 0xF } ;
counter |= lookup[lsb & 0xf] << 4 | lookup[lsb >> 4] ;
Вы можете даже упаковать таблицу поиска и использовать всего 8 байтов (0x80, 0xC4 и т. Д.):
static const uint8_t lookup[] = { 0x80, 0xC4,
0xA2, 0xE6,
0x91, 0xD5,
0xB3, 0xF7 } ;
uint8_t msnib = ( lsb & 0x01 ) ? lookup[(lsb & 0xf) >> 1] >> 4 :
lookup[(lsb & 0xf) >> 1] & 0xf ;
uint8_t lsnib = ( lsb & 0x10 ) ? lookup[(lsb & 0xf0) >> 5] >> 4 :
lookup[(lsb & 0xf0) >> 5] & 0xf ;
counter |= (lsnib | msnib << 4) ;
Но уменьшение размера справочной таблицы вряд ли будет оправдано увеличением размера кода для результирующей дополнительной битовой манипуляции - и это немного «слишком умно» - потребовалось некоторое время, чтобы его получитьright!
Преимущество первого метода состоит в том, что его можно применять к произвольному числу битов.Оба решения для справочных таблиц могут быть расширены до любого размера слова, кратного 4 битам, без изменения размера справочной таблицы, поэтому они хорошо масштабируются.
Сравнительный анализ
Я тестировал каждую реализацию на https://godbolt.org/, установленном на AVR GCC 4.6.4, с использованием трех различных настроек оптимизации.Счетчик команд исключает добавленный код входа / выхода функции, чтобы сделать его компилируемым, и представляет только инструкции, сгенерированные из исходного кода в этом ответе.
| | Instruction Count | |
|Algorithm | No Opt | -O3 | -Os | + Data (bytes)|
|----------|:------:|:---:|:---:|:-------------:|
| Loop | 38 | 88 | 23 | 0 |
| LookUp16 | 59 | 38 | 37 | 16 |
| LookUp8 | 137 | 65 | 62 | 8 |
Тест мало говорит о времени выполнения, но, если размер кода важен, алгоритм цикла с оптимизацией пространства (-Os
), вероятно, является лучшим выбором.
Без сомнения, справочная таблица работает быстрее, независимо от уровня оптимизации, и 16-байтовая справочная таблица с любой из этих оптимизаций может быть разумным балансом.Для -O3
он в целом меньше и быстрее, чем развернутый цикл из 88 команд.Он также имеет явное преимущество в том, что размер кода гораздо менее изменчив с настройками оптимизации, которые могут свести к минимуму неожиданности при переключении между сборками отладки и выпуска.
8-байтовый поиск имеет мало достоинств, возможно, иного, чем то, что он довольноинтересно.