【问题标题】:what is the right way to access builtin_bswap functions?访问 builtin_bswap 函数的正确方法是什么?
【发布时间】:2021-04-03 21:12:09
【问题描述】:

我有一个使用数据库的应用程序,该数据库以大端顺序存储数据。为了跨硬件平台可移植地访问这些数据,我使用了 config.h 模块中定义的 4 个宏:

word(p) - gets a big-endian 16 bit value at pointer p as a native 16-bit value.
putword(p, w) - stores a native 16-bit variable (w) to pointer p as 16-bit big-endian.
dword(p) and putdword(p, d) do the same for 32-bit values

这一切都很好,但在 little-endian 机器上的宏使用蛮力“移位和掩码”方法。

无论如何,Linux 上似乎有 builtin_bswap16 和 builtin_bswap32 函数可以更有效地执行此操作(作为内联汇编代码?)。那么,对我的 word/putword 宏进行编码以便它们在 X86_64 linux 机器上使用这些内置函数的正确方法是什么?将我的宏编码为 htons/l 函数调用是否可以有效地做同样的事情 - 是否有必要启用编译器优化才能使这些解决方案中的任何一个工作?如果它使 gdb 无用,我宁愿不优化。

【问题讨论】:

  • endian(3)glibc endian.hnewlib endian.hgspd/bits.hstores a native 16-bit variable (w) to pointer p as 16-bit big-endian. 是否保证 p 对齐到 16 位? w 是否保证对齐到 16 位?
  • 是的,16 位和 32 位对齐是有保证的。该数据库起源于 IBM Series/1,它是大端的,不支持未对齐的 16 位和 32 位数据提取/存储。事实上,数据库是系统的一部分,其中一些遗留的 Series/1 汇编代码仍然通过软件仿真层运行。与只需要字节交换数据库字段的新代码相比,该仿真层可能会从快速字节交换中获得更多好处......

标签: linux gcc endianness


【解决方案1】:

嗯。我编写了一个简单的测试程序,没有使用特殊的包含文件,而是直接调用 __builtin_swap... 函数(参见下面的“快速...”宏)。这一切都行得通。当我在 gdb 中反汇编代码时,我看到快速...宏在 4-5 条汇编指令中执行,对于最坏情况的“dword”宏,最多需要 27 条指令。几乎不费吹灰之力的相当巧妙的改进。

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;

#define word(a)       (ushort) ( (*((uchar *)(a)) << 8) |          \
                                 (*((uchar *)(a) + 1)) )
#define putword(a,w)  *((char *)(a))   =  (char) (((ushort)((w) >>  8)) & 0x00ff), \
                      *((char *)(a)+1) =  (char) (((ushort)((w) >>  0)) & 0x00ff)
#define dword(a) (uint)  ( ((uint)(word(a)) << 16) |      \
                             ((uint)(word(((uchar *)(a) + 2)))) )
#define putdword(a,d) *((char *)(a))   =  (char) (((uint)((d) >> 24)) & 0x00ff), \
                      *((char *)(a)+1) =  (char) (((uint)((d) >> 16)) & 0x00ff), \
                      *((char *)(a)+2) =  (char) (((uint)((d) >>  8)) & 0x00ff), \
                      *((char *)(a)+3) =  (char) (((uint)((d) >>  0)) & 0x00ff)

#define fastword(a)   (ushort) __builtin_bswap16(* ((ushort *) a));
#define fastputword(a, w)  *((ushort *) a) =  __builtin_bswap16((ushort)w);
#define fastdword(a)   (uint) __builtin_bswap32(* ((uint *) a));
#define fastputdword(a, d)  *((uint *) a) =  __builtin_bswap32((uint)d);

int main()
{
unsigned short s1, s2, s3;
unsigned int i1, i2, i3;

        s1 = 0x1234;
        putword(&s2, s1);
        s3 = word(&s2);
        i1 = 0x12345678;
        putdword(&i2, i1);
        i3 = dword(&i2);
        printf("s1=%x, s2=%x, s3=%x, i1=%x, i2=%x, i3=%x\n", s1, s2, s3, i1, i2, i3);

        s1 = 0x1234;
        fastputword(&s2, s1);
        s3 = fastword(&s2);
        i1 = 0x12345678;
        fastputdword(&i2, i1);
        i3 = fastdword(&i2);
        printf("s1=%x, s2=%x, s3=%x, i1=%x, i2=%x, i3=%x\n", s1, s2, s3, i1, i2, i3);
}

【讨论】:

  • *((char *)(a)) = 如果您将使用普通类型而不是单字节,编译器将足够聪明,可以将其优化为相同的代码...为什么您到处都使用char?代码不等效-如果a 未与ushortuint 对齐,您将得到段错误-因此您看到的“改进”,更少的指令,因为编译器可以利用对齐的指令。它与__builtin_bswap* 的关系较小,但更多的是您使用*((ushort *) a) = 而不是*(char*)a = 分配。
  • 我尝试修改我的测试程序以专门通过 __builtin_bswap32 强制非对齐 dword 访问,并且它有效。所以,我猜 X86_64 机器在获取 short 或 int 时并不关心 16 位和 32 位内存对齐。
【解决方案2】:

我只会使用htons, htonl 和朋友。它们的可移植性要高得多,并且很可能任何给定 libc 的作者都将它们实现为内联函数或调用 __builtin 内在函数或内联 asm 或其他任何东西的宏,从而导致应该是近乎最佳的实现对于那台特定的机器。 See what is generated in godbolt's setup,我认为这是某种 Linux/glibc 风格。

您确实需要对它们进行内联优化进行编译,否则它会生成一个普通的函数调用。但是即使-Og 也将它们内联,并且不应该过多地扰乱您的调试。无论如何,如果您在完全没有优化的情况下进行编译,那么您的整个程序将非常低效,以至于调用htons 的额外指令肯定是您最不必担心的。

【讨论】:

  • 嗯。我对 __builtin_bswap 函数的使用仅限于我的 config.h 文件中的宏定义 - 仅在已知 __builtin_bswap 可用的系统上。因此,如果使用 htons 需要优化,那么我宁愿直接进入“源”。至于我是否会看到任何速度优势,我想这还有待观察 - 但是(请参阅我对原始帖子的评论),对于 Series/1 机器仿真,我认为好处将是显着的。跨度>
猜你喜欢
  • 2014-08-16
  • 1970-01-01
  • 2019-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多