Эта функция в порядке, чтобы преобразовать двоичный код в целое число в c - PullRequest
2 голосов
/ 21 мая 2019

Я использую эту функцию для преобразования 8-разрядного двоичного числа, представленного в виде логического массива, в целое число.Это эффективно?Я использую это во встроенной системе.Работает нормально, но меня интересуют некоторые мнения или предложения по улучшению (или замене), если таковые имеются.

uint8_t b2i( bool *bs ){

            uint8_t ret = 0;

            ret  = bs[7] ?   1 : 0;
            ret += bs[6] ?   2 : 0;
            ret += bs[5] ?   4 : 0;
            ret += bs[4] ?   8 : 0;
            ret += bs[3] ?  16 : 0;
            ret += bs[2] ?  32 : 0;
            ret += bs[1] ?  64 : 0;
            ret += bs[0] ? 128 : 0;

            return ret;
        }

Ответы [ 2 ]

3 голосов
/ 21 мая 2019

Невозможно сказать без конкретной системы. Разберите код и посмотрите, что вы получили. Оцените ваш код в конкретной системе. Это ключ к пониманию ручной оптимизации.

Как правило, есть много соображений. Размер слова данных процессора, набор команд, производительность оптимизатора компилятора, прогноз ветвления (если есть), кэш данных (если есть) и т. Д. И т. Д.

Чтобы код работал оптимально, независимо от размера слова данных, вы можете изменить uint8_t на uint_fast8_t. То есть, если вам не нужно ровно 8 бит, оставьте это как uint8_t.

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

Худшая проблема с кодом - многочисленные ветки. Это может вызвать узкое место.

Ваш код приводит к следующему машинному коду x86 gcc -O2:

b2i:
        cmp     BYTE PTR [rdi+6], 0
        movzx   eax, BYTE PTR [rdi+7]
        je      .L2
        add     eax, 2
.L2:
        cmp     BYTE PTR [rdi+5], 0
        je      .L3
        add     eax, 4
.L3:
        cmp     BYTE PTR [rdi+4], 0
        je      .L4
        add     eax, 8
.L4:
        cmp     BYTE PTR [rdi+3], 0
        je      .L5
        add     eax, 16
.L5:
        cmp     BYTE PTR [rdi+2], 0
        je      .L6
        add     eax, 32
.L6:
        cmp     BYTE PTR [rdi+1], 0
        je      .L7
        add     eax, 64
.L7:
        lea     edx, [rax-128]
        cmp     BYTE PTR [rdi], 0
        cmovne  eax, edx
        ret

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

uint8_t b2i (const bool bs[8])
{
  uint8_t result = 0;
  for(size_t i=0; i<8; i++)
  {
    result |= bs[8-1-i] << i;
  }
  return result;
}

(в идеале массив bool должен сначала располагаться из LSB, но это изменило бы значение кода по сравнению с оригиналом)

Что дает этот машинный код вместо:

b2i:
        lea     rsi, [rdi-8]
        mov     rax, rdi
        xor     r8d, r8d
.L2:
        movzx   edx, BYTE PTR [rax+7]
        mov     ecx, edi
        sub     ecx, eax
        sub     rax, 1
        sal     edx, cl
        or      r8d, edx
        cmp     rax, rsi
        jne     .L2
        mov     eax, r8d
        ret

Больше инструкций, но меньше ветвлений. Вероятно, он будет работать лучше, чем ваш код на x86 и других высокопроизводительных процессорах с предсказанием ветвлений и кэшем инструкций. Но хуже, чем ваш код на 8-битном микроконтроллере, где учитывается только общее количество инструкций.

1 голос
/ 21 мая 2019

Вы также можете сделать это с помощью циклов и битовых сдвигов, чтобы уменьшить количество повторений кода:

int b2i(bool *bs) {
    int ret = 0;
    for (int i = 0; i < 8; i++) {
        ret = ret << 1;
        ret += bs[i];
    }
    return ret;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...