问题描述
在一个相当大且复杂的C程序中,运行时间是第一要务,我必须决定如何编写这样的代码段:
for (int i=0; i < md->global_grid[ic[0]][ic[1]][ic[2]].parts_num; i++)
{
if (md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core.GroupID == RESERVED_GROUP)
{
md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core.GroupID = GroupID;
fmd_real_t mass = md->potsys.atomkinds[md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core.atomkind].mass;
for (int d=0; d<3; d++)
md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core.v[d] -= MomentumSum[d] / (AtomsNum * mass);
}
}
使用下面的pc
这样的指针,可以使此代码更具可读性和紧凑性:
for (int i=0; i < md->global_grid[ic[0]][ic[1]][ic[2]].parts_num; i++)
{
if (md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core.GroupID == RESERVED_GROUP)
{
particle_core_t *pc = &md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core;
pc->GroupID = GroupID;
fmd_real_t mass = md->potsys.atomkinds[pc->atomkind].mass;
for (int d=0; d<3; d++)
pc->v[d] -= MomentumSum[d] / (AtomsNum * mass);
}
}
但是取消引用pc
会占用一些cpu时间吗?我通常使用第一种形式,有时使用第二种形式,但不知道哪种更好。我使用-O3
的gcc进行优化。
我知道测量运行时间并进行比较可能会提供一个答案,但是了解经验丰富且专业的程序员始终会非常有帮助。特别是,仅比较时间并不能说明为什么一种形式更快。
解决方法
看看jtbandes的godbolt example中的程序集。这是用于内部循环可读版本的gcc的x86-64程序集:
particle_core_t *pc = &md->global_grid[ic[0]][ic[1]][ic[2]].parts[i].core;
pc->GroupID = GroupID;
fmd_real_t mass = md->potsys.atomkinds[pc->atomkind].mass;
for (int d=0; d<3; d++)
pc->v[d] -= MomentumSum[d] / (AtomsNum * mass);
gcc非常聪明,可以看到d
上的循环进行了3次迭代,因此将其展开。还可以看到,每次迭代中的lhs都在同一数组中,因此它可以将数组地址有效地存储在rcx
中,而不是重复地取消对pc->v
的引用。
mov rcx,QWORD PTR [rax+8] ; rcx = pc->v.
mov DWORD PTR [rax],ebp
pxor xmm0,xmm0
add rdx,1
movsx rax,DWORD PTR [rax+4]
mov rsi,QWORD PTR [r11+8]
cvtsi2sd xmm0,r8d
movsd xmm2,QWORD PTR [rbx] ; Load xmm2 = MomentumSum[0].
movsd xmm1,QWORD PTR [rcx] ; Load xmm1 = pc->v[0].
lea rax,[rax+rax*2]
lea rax,[rsi+rax*8]
mulsd xmm0,QWORD PTR [rax+16] ; Compute xmm0 = AtomsNum * mass.
movsx rax,DWORD PTR [r10]
mov rax,QWORD PTR [r12+rax*8]
divsd xmm2,xmm0 ; xmm2 /= xmm0
subsd xmm1,xmm2 ; xmm1 -= xmm2
movsd QWORD PTR [rcx],xmm1 ; Store pc->v[0] = xmm1.
movsd xmm2,QWORD PTR [rbx+8]
movsd xmm1,QWORD PTR [rcx+8]
divsd xmm2,xmm0
subsd xmm1,xmm2
movsd QWORD PTR [rcx+8],xmm1 ; Store pc->v[1] = xmm1.
movsd xmm1,QWORD PTR [rbx+16]
divsd xmm1,xmm0
movsd xmm0,QWORD PTR [rcx+16]
subsd xmm0,xmm1
movsd QWORD PTR [rcx+16],xmm0 ; Store pc->v[2] = xmm1.
movsx rcx,DWORD PTR [r10+4]
mov r9,QWORD PTR [rax+rcx*8]
movsx rcx,DWORD PTR [r10+8]
lea rax,[rcx+rcx*2]
lea rsi,[r9+rax*8]
mov edi,DWORD PTR [rsi]