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