使用pragma pack(1)时是否存在性能问题?
我们的头文件在我们的大多数结构中使用#pragma pack(1)
(用于网络和文件I / O)。 据我所知,它将结构的对齐方式从默认的8个字节更改为1个字节的对齐方式。
假设一切都在32位Linux(也许是Windows)中运行,那么这种打包对齐是否有任何性能损失?
我不关心库的可移植性,但更关注文件和网络I / O与不同#pragma包的兼容性以及性能问题。
内存访问在字对齐的内存地址发生时最快。 最简单的例子是以下结构(@Didier也使用过):
struct sample { char a; int b; };
默认情况下,GCC插入填充,因此a位于偏移0处,b位于偏移4处(字对齐)。 没有填充,b不是字对齐的,访问速度较慢。
慢多少?
- 对于32位x86,根据Intel 64和IA32架构软件开发人员手册 :
处理器需要两次内存访问才能进行未对齐的内存访问; 对齐访问只需要一次内存访问。 跨越4字节边界的字或双字操作数或跨越8字节边界的四字操作数被认为是未对齐的,并且需要两个单独的存储器总线周期来进行访问。
与大多数性能问题一样,您必须对应用程序进行基准测试,以了解这在实践中存在多少问题。
- 根据维基百科 ,像SSE2这样的x86扩展需要字对齐。
- 许多其他架构需要字对齐(如果数据结构不是字对齐的,则会产生SIGBUS错误)。
关于可移植性:我假设你正在使用#pragma pack(1)
这样你就可以通过线路和磁盘发送结构,而不必担心不同的编译器或平台包装结构。 这是有效的,但是,要记住以下几个问题:
- 这对于处理大端和小端问题没有任何作用。 您可以通过在结构中的任何整数,无符号等上调用htons函数系列来处理这些问题。
- 根据我的经验,在应用程序代码中使用打包的可序列化结构并不是很有趣。 它们很难在不破坏向后兼容性的情况下进行修改和扩展,并且如前所述,存在性能损失。 考虑将打包的,可序列化的结构体内容转移到等效的非打包,可扩展的结构中进行处理,或者考虑使用完整的序列化库,如Protocol Buffers (具有C绑定 )。
是。 绝对有。
例如,如果定义结构:
struct dumb { char c; int i; };
然后,无论何时访问成员i,CPU都会变慢,因为32位值i无法以原生的对齐方式访问。 为简单起见,假设CPU必须从内存中获取3个字节,然后从下一个位置获取另外1个字节,以将值从内存传输到CPU寄存器。
声明结构时,大多数编译器在成员之间插入填充字节,以确保它们与内存中的适当地址对齐(通常填充字节是类型大小的倍数)。 这使编译器能够在访问这些成员时获得优化访问权限。
#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的成本高于处理成本时,才打包它。
从技术上讲,是的,它会影响性能,但仅限于内部处理。 如果您需要为网络/文件IO打包的结构,则在打包需求和内部处理之间存在平衡。 通过内部处理,我的意思是,你对IO之间的数据所做的工作。 如果你只进行很少的处理,你就不会在性能方面损失太多。 否则,您可能希望对正确对齐的结构进行内部处理,并仅在执行IO时“打包”结果。 或者您可以切换到仅使用默认对齐结构,但您需要确保每个人以相同的方式对齐它们(网络和文件客户端)。
某些机器代码指令在32位或64位(甚至更多)上运行,但期望数据在内存地址上对齐。 如果他们不是,他们必须在内存上执行多个读/写cyce来执行他们的任务。 性能达到的程度在很大程度上取决于您对数据的处理方式。 如果构建大型结构数组并对它们执行大量计算,它可能会变大。 但是,如果您只将数据存储一次只是为了在其他时间将其读回,无论如何将其转换为字节流,那么它可能几乎不可能。