【问题标题】:Is this function ok to convert binary to integer in c这个函数可以在c中将二进制转换为整数吗
【发布时间】:2019-05-21 06:24:14
【问题描述】:

我正在使用此函数将表示为布尔数组的 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;
        }

【问题讨论】:

  • 如果代码工作那么在codereview.stackexchange.com上会更好
  • 您是否将其确定为瓶颈?在那之前,不要打扰。
  • 我的直觉是,如果您因此而遇到性能问题,那么真正的问题在于数据结构的选择。
  • 如果传递的数组实际上少于 8 个元素,您期望会发生什么?

标签: c data-conversion


【解决方案1】:

如果没有特定的系统,就不可能说。反汇编代码,看看你得到了什么。在特定系统上对您的代码进行基准测试。这是理解手动优化的关键。

一般来说,有很多考虑因素。 CPU 的数据字长、指令集、编译器优化器性能、分支预测(如果有)、数据缓存(如果有)等。

要使代码无论数据字长如何都能以最佳方式执行,您可以将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 和其他具有分支预测和指令缓存的高端 CPU 上的代码执行得更好。但比您在仅计算指令总数的 8 位微控制器上的代码更糟糕。

【讨论】:

  • @FridaKahlo 我从来没有用过那些老爱好者的东西,但 Godbolt 网站支持 gcc AVR 端口:godbolt.org/z/Qp9ZFA。不清楚哪个代码在那里更好。奇怪的是,编译器在优化我的代码时添加了一些“zero_reg”,我想这是一个零页 RAM 分配的变量(?)。这是许多 8 苦涩的另一个性能方面,它们在零页中使用纯 8 位指令集表现更好。
  • @FridaKahlo 如果你想要我的意见,那就是:所有 8 种苦味剂都是过时的废话,我们在 10 年前就没有使用它们的理由了。这来自一个在过去 15 年里一直在专业地进行大量编程的人。您可以以相同的价格获得功能强大 100 倍的 MCU,例如 Atmel SAM (Cortex M)。它在各个方面都更好 - 包括易用性。 8 位实际上很难用 C 语言编程,它们都是在设计时考虑到了汇编程序。
  • @FridaKahlo 我是说当你有 SAM(或其他 Cortex M 系列)时,没有理由使用 AVR。
【解决方案2】:

你也可以通过循环和位移来减少代码重复:

int b2i(bool *bs) {
    int ret = 0;
    for (int i = 0; i < 8; i++) {
        ret = ret << 1;
        ret += bs[i];
    }
    return ret;
}

【讨论】:

  • 谢谢。 “展开循环”不是更快吗?
  • @FridaKahlo 这是可能的。现代编译器会自己进行优化。我不知道它在嵌入式环境中可供您使用的编译器上的情况如何,但找出答案的最佳方法是对其进行测试。
猜你喜欢
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
  • 1970-01-01
  • 2012-04-10
  • 1970-01-01
相关资源
最近更新 更多