说我有这样的结构:
struct tmp {
unsigned char arr1[10];
unsigned char arr2[10];
int i1;
int i2;
unsigned char arr3[10];
unsigned char arr4[10];
};
其中哪些会更快?
((1)将整个结构设置为0,然后将成员填充为:
struct tmp t1;
memset(&t1, 0, sizeof(struct tmp));
t1.i1 = 10;
t1.i2 = 20;
memcpy(t1.arr1, "ab", sizeof("ab"));
// arr2, arr3 and arr4 will be filled later.
OR
((2)Memset单独的变量:
struct tmp t1;
t1.i1 = 10;
t1.i2 = 20;
memcpy(t1.arr1, "ab", sizeof("ab"));
memset(t1.arr2, 0, sizeof(t1.arr2); // will be filled later
memset(t2.arr3, 0, sizeof(t1.arr3); // will be filled later
memset(t2.arr4, 0, sizeof(t1.arr4); // will be filled later
就性能而言,多次调用memset(在结构的不同成员上)比对单个memset的调用(在整个结构上)要快/慢。
如果不考虑特定的系统,对此进行讨论就没有真正的意义,除非您确实有一个性能瓶颈,思考这些事情也不会富有成果。我仍然可以尝试。
对于“通用计算机”,您必须考虑:
Aligned access一次性访问大量数据通常会更好。在潜在的未对齐情况下,无论数据有多大,要处理的开销代码都大致相同。从理论上假设此代码中的所有访问恰好都未对齐,则1个memset调用比3个更好。
此外,我们可以假定结构的第一项是对齐的,但是对于结构内的任何单个成员,我们都不能假定它是对齐的。链接器会将结构分配到对齐的地址,然后可能在其内部的任何位置添加填充以补偿未对齐。
您的结构在声明时不考虑对齐问题,因此这里将成为问题-编译器将插入大量填充。
(另一方面,整个结构上的memset也将覆盖填充字节,这是一小部分的开销代码。)
数据缓存使用从顶部到底部访问相邻内存区域比从代码中的多个位置访问其片段更“缓存友好”。随后访问连续内存意味着计算机可以将很多数据加载到缓存中,而不是从RAM中获取数据,这比较慢。
指令缓存的使用和分支预测在这种情况下不是很相关,因为代码基本上只是在进行原始副本,并且是无分支的。
生成的机器指令数量这总是很好地粗略地表明代码的速度。显然,有些指令要比其他指令慢很多,但是更少的指令通常意味着更快的代码。用gcc x86_64 -O3分解两个函数,然后我得到:
func1:
movabs rax, 85899345930
pxor xmm0, xmm0
movups XMMWORD PTR [rdi+16], xmm0
mov QWORD PTR [rdi+20], rax
mov eax, 25185
movups XMMWORD PTR [rdi], xmm0
movups XMMWORD PTR [rdi+32], xmm0
mov WORD PTR [rdi], ax
ret
func2:
movabs rax, 85899345930
xor edx, edx
xor ecx, ecx
xor esi, esi
mov QWORD PTR [rdi+20], rax
mov eax, 25185
mov WORD PTR [rdi], ax
mov BYTE PTR [rdi+2], 0
mov QWORD PTR [rdi+10], 0
mov WORD PTR [rdi+18], dx
mov QWORD PTR [rdi+28], 0
mov WORD PTR [rdi+36], cx
mov QWORD PTR [rdi+38], 0
mov WORD PTR [rdi+46], si
ret
这很好地表明了以前的代码效率更高,并且还应该对数据缓存更友好,因此,如果(1)的速度不是很快,这会让我感到惊讶。
还请注意,如果使用静态存储持续时间声明此结构,则将零输出“外包”到程序设置.bss
的CRT部分,并在调用main()之前执行。这样就不需要这些内存集了。以稍慢的启动速度为代价,但总体上来说程序更快。