C 使用pragma包(1)时是否存在性能问题?

C 使用pragma包(1)时是否存在性能问题?,c,gcc,C,Gcc,我们的头在大多数结构(用于网络和文件I/O)周围使用#pragma pack(1)。我知道它将结构的对齐方式从默认的8字节更改为1字节 假设所有东西都在32位Linux(可能也是Windows)中运行,那么这种打包方式是否会对性能造成影响 我不关心库的可移植性,更关心的是文件和网络I/O与不同的#pragma包的兼容性,以及性能问题。声明结构时,大多数编译器在成员之间插入填充字节,以确保它们与内存中的适当地址对齐(通常填充字节是类型大小的倍数)。这使编译器在访问这些成员时具有优化的访问权限 #p

我们的头在大多数结构(用于网络和文件I/O)周围使用
#pragma pack(1)
。我知道它将结构的对齐方式从默认的8字节更改为1字节

假设所有东西都在32位Linux(可能也是Windows)中运行,那么这种打包方式是否会对性能造成影响


我不关心库的可移植性,更关心的是文件和网络I/O与不同的#pragma包的兼容性,以及性能问题。

声明结构时,大多数编译器在成员之间插入填充字节,以确保它们与内存中的适当地址对齐(通常填充字节是类型大小的倍数)。这使编译器在访问这些成员时具有优化的访问权限

#pragma pack(1)
指示编译器使用特定对齐方式打包结构成员。此处的
1
告诉编译器不要在成员之间插入任何填充

因此,是的,有一个明确的性能损失,因为你强迫编译器做一些超出其性能优化的事情。此外,一些平台要求对象在特定边界对齐,并且使用非照明结构可能会给你带来分段错误。

理想情况下,最好避免更改默认的自然对齐规则。但如果根本无法避免“pragma pack”指令(如您的情况),则必须在定义需要紧密填充的结构后恢复原始填充方案

例如:

//push current alignment rules to internal stack and force 1-byte alignment boundary
#pragma pack(push,1)  

/*   definition of structures that require tight packing go in here   */

//restore original alignment rules from stack    
#pragma pack(pop)

它取决于底层体系结构及其处理未对齐地址的方式

x86可以优雅地处理未对齐的地址,尽管这会降低性能,而ARM等其他体系结构可能会调用对齐错误(
SIGBUS
),甚至会将未对齐的地址“舍入”到最近的边界,在这种情况下,代码将以可怕的方式失败


底线是,只有当您确信底层架构将处理未对齐的地址,并且网络I/O成本高于处理成本时,才打包它。

是的。绝对有

例如,如果定义结构:

struct dumb {
    char c;
    int  i;
};

然后,无论何时访问成员i,CPU都会变慢,因为32位值i无法以本机对齐方式访问。为了简单起见,假设CPU必须从内存中获取3个字节,然后从下一个位置获取1个字节,才能将值从内存传输到CPU寄存器。

从技术上讲,是的,这会影响性能,但仅限于内部处理。如果您需要为网络/文件IO打包的结构,那么打包要求和内部处理之间就有一个平衡。所谓内部处理,我的意思是,您在IO之间对数据所做的工作。如果您只做很少的处理,您在性能方面不会损失太多。否则,您可能希望在正确对齐的结构上执行内部处理,并在执行IO时仅“打包”结果。或者您可以切换到仅使用默认对齐的结构,但您需要确保每个人都以相同的方式对齐它们(网络和文件客户端).

某些机器代码指令在32位或64位(或更高)上运行,但期望数据在内存地址上对齐。如果不对齐,则必须在内存上执行多个读/写循环才能执行任务。
性能影响的大小在很大程度上取决于您对数据所做的操作。如果您构建大型结构数组并对其执行大量计算,它可能会变得很大。但如果您只存储一次数据,只是为了在其他时间将其读回,并将其转换为字节流,那么它可能几乎不会被注意到。

内存访问当ss发生在字对齐的内存地址时,它是最快的。最简单的例子是下面的struct(@Didier也使用它):

默认情况下,GCC插入填充,因此a位于偏移量0,b位于偏移量4(字对齐)。没有填充,b不与字对齐,访问速度较慢

慢了多少

  • 对于32位x86,根据:处理器需要两个内存 进行未对齐内存访问的访问;对齐的访问只需要一次 内存访问。一种跨越4字节边界的字或双字操作数 跨越8字节边界的四字操作数被视为未对齐且 访问需要两个独立的内存总线周期。与大多数性能问题一样,您必须对应用程序进行基准测试,以了解这在实践中有多大问题
  • 据介绍,像SSE2这样的x86扩展需要字对齐
  • 许多其他体系结构需要字对齐(如果数据结构没有字对齐,则会产生SIGBUS错误)
关于可移植性:我假设您使用的是
#pragma pack(1)
,这样您就可以跨线路和磁盘发送结构,而不必担心不同的编译器或平台会以不同的方式打包结构。这是有效的,但是,有几个问题需要记住:

  • 这对处理大端和小端问题没有任何作用。您可以通过在结构中的任何整数、无符号等上调用函数族来处理这些问题
  • <> LI>在我的经验中,在应用程序代码中使用打包的、可串行化的结构不是很有趣。它们很难修改和扩展而不会破坏向后兼容性,正如已经指出的,存在性能上的损失。考虑将打包好的、可序列化的结构的内容转换成等效的非打包、扩展的内容。LE结构用于处理,或者考虑使用一个完整的序列化库,如(有).
在一些平台上,如ARM Cortex-M0,16位加载/sto
struct sample {
   char a;
   int b;
};
void UpdateS(S* s)
{
 s->total = s->a + s->b;
}

void UpdateP(P* p)
{
 p->total = p->a + p->b;
}
UpdateS                       UpdateP
Intel Itanium

adds  r31 = r32, 4            adds  r31 = r32, 4
adds  r30 = r32  8 ;;         adds  r30 = r32  8 ;;
ld4   r31 = [r31]             ld1   r29 = [r31], 1
ld4   r30 = [r30] ;;          ld1   r28 = [r30], 1 ;;
                              ld1   r27 = [r31], 1
                              ld1   r26 = [r30], 1 ;;
                              dep   r29 = r27, r29, 8, 8
                              dep   r28 = r26, r28, 8, 8
                              ld1   r25 = [r31], 1
                              ld1   r24 = [r30], 1 ;;
                              dep   r29 = r25, r29, 16, 8
                              dep   r28 = r24, r28, 16, 8
                              ld1   r27 = [r31]
                              ld1   r26 = [r30] ;;
                              dep   r29 = r27, r29, 24, 8
                              dep   r28 = r26, r28, 24, 8 ;;
add   r31 = r30, r31 ;;       add   r31 = r28, r29 ;;
st4   [r32] = r31             st1   [r32] = r31
                              adds  r30 = r32, 1
                              adds  r29 = r32, 2 
                              extr  r28 = r31, 8, 8
                              extr  r27 = r31, 16, 8 ;;
                              st1   [r30] = r28
                              st1   [r29] = r27, 1
                              extr  r26 = r31, 24, 8 ;;
                              st1   [r29] = r26
br.ret.sptk.many rp           br.ret.sptk.many.rp

...
[examples from other hardware]
...