在不降低性能的情况下提高代码可读性(对于 C 中的这个片段)
Increasing code readability without a decrease in performance (for this snippet in C)
在一个相当大且复杂的 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 时间吗?我通常使用第一种形式,有时使用第二种形式,但不知道哪种更好。我使用gcc的-O3
进行优化。
我知道测量 运行 次并比较它们可能会提供答案,但了解经验丰富的专业程序员的想法总是非常有帮助的。特别是,仅仅比较时间并不能说明为什么一种形式更快。
看看 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]
在一个相当大且复杂的 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 时间吗?我通常使用第一种形式,有时使用第二种形式,但不知道哪种更好。我使用gcc的-O3
进行优化。
我知道测量 运行 次并比较它们可能会提供答案,但了解经验丰富的专业程序员的想法总是非常有帮助的。特别是,仅仅比较时间并不能说明为什么一种形式更快。
看看 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]