【问题标题】:Increasing code readability without a decrease in performance (for this snippet in C)在不降低性能的情况下提高代码可读性(对于 C 中的此代码段)
【发布时间】:2020-11-05 21:17:03
【问题描述】:

在一个相当大且复杂的 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 进行优化。

我知道测量运行时间并进行比较可能会提供答案,但了解有经验的专业程序员的想法总是很有帮助的。特别是,仅仅比较时间并不能说明为什么一种形式更快。

【问题讨论】:

  • 取消引用 pc 可能需要 CPU 时间,但 md-&gt;global_grid[ic[0]][ic[1]][ic[2]].parts[i].core 也是如此...您是否尝试查看生成的程序集以查看它是相同还是不同?
  • @jtbandes,在第一个中,我完全依赖编译器进行优化。不,我还没有看到生成的程序集。我在组装方面没有太多经验。
  • 也许这会帮助您入门:godbolt.org/z/aTrWMh
  • 首先,您还没有证明第二种形式实际上更慢。 但是取消引用 pc 不会占用一些 CPU 时间吗?我通常使用第一种形式,有时使用第二种形式,但不知道哪个更好。如果你不能说,没关系。 总是编写可读的代码。处理性能问题当它们变得既明显又引起实际问题时。牺牲可读性只是为了让一个需要三个小时的过程减少 50 毫秒,这比浪费时间更糟糕 - 从字面上看。它使错误更有可能出现 - 并使它们更难定位和修复。
  • @AndrewHenle,我认为您是对的,尽管在这种情况下,该过程可能需要大约一周的时间。非常感谢。很高兴看到喜欢互相帮助的人。 :)

标签: c performance gcc readability


【解决方案1】:

查看 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-&gt;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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    相关资源
    最近更新 更多