如何提高内联函数效率?

How to improve inline function efficiency?

我分析了我的代码,发现一个内联函数占用了大约 8% 的样本。作用是将矩阵下标转换为索引。它很像 matlab 函数 sub2ind.

inline int sub2ind(const int sub_height, const int sub_width, const int width) {
    return sub_height * width + sub_width;
}

我想编译器不会执行内联扩展,但我不知道如何检查。

有什么办法可以改善吗?还是明确让编译器进行内联展开?

如@user3528438 所述,您可以查看程序集输出。考虑以下示例:

inline int sub2ind(const int sub_height, const int sub_width, const int width) {
    return sub_height * width + sub_width;
}

int main() {
    volatile int n[] = {1, 2, 3};
    return sub2ind(n[0], n[1], n[2]);
}

未经优化编译它 (g++ -S test.cc) 导致以下代码 sub2ind 未内联:

main:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    , %rsp
    movl    , -16(%rbp)
    movl    , -12(%rbp)
    movl    , -8(%rbp)
    movq    -16(%rbp), %rax
    movq    %rax, -32(%rbp)
    movl    -8(%rbp), %eax
    movl    %eax, -24(%rbp)
    movl    -24(%rbp), %edx
    movl    -28(%rbp), %ecx
    movl    -32(%rbp), %eax
    movl    %ecx, %esi
    movl    %eax, %edi
    call    _Z7sub2indiii ; call to sub2ind
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

在使用优化 (g++ -S -O3 test.cc) 进行编译时导致 sub2ind 被内联并且大部分被优化掉了:

main:
.LFB1:
    .cfi_startproc
    movl    , -24(%rsp)
    movl    , -20(%rsp)
    movq    -24(%rsp), %rax
    movl    , -16(%rsp)
    movq    %rax, -40(%rsp)
    movl    , -32(%rsp)
    movl    -32(%rsp), %eax
    movl    -36(%rsp), %edx
    movl    -40(%rsp), %ecx
    imull   %ecx, %eax
    addl    %edx, %eax
    ret
    .cfi_endproc

因此,如果您确信您的函数未内联,请首先确保在编译器选项中启用优化。

你还记得优化编译吗?一些编译器具有强制内联的属性,即使编译器不想这样做:请参阅 this question.

但它可能已经;您可以尝试让您的编译器输出汇编代码并尝试以这种方式进行检查。

索引计算可能会占用您的大部分时间,这并非难以置信 -- 例如如果您的算法是从矩阵读取,进行一些计算,然后写回,那么索引计算确实占了计算时间的很大一部分。

或者,您编写代码的方式编译器无法证明 width 在整个循环中保持不变*,因此每次都必须从内存中重新读取它,只是为了确定。尝试将 width 复制到局部变量并在您的内部循环中使用它。

现在,您已经说过这会占用您 8% 的时间 — 这意味着您不太可能可能获得超过 8% 的改进运行时,可能更少。如果那真的值得,那么要做的事情可能是从根本上改变你遍历数组的方式。

例如

  • 如果您倾向于以线性方式访问矩阵,您可以编写某种二维迭代器 class,您可以向上、向下、向左或向右前进,并且它将使用加法到处而不是乘法
  • 同样的事情,但是写一个 "index" class 只是保存数字而不是假装是一个指针
  • 如果 width 是一个编译时常量,你可以明确地让它如此,例如作为模板参数,您的编译器可能能够使用乘法
  • 做更聪明的事情

*:您可能做了一些愚蠢的事情,比如将矩阵的数据结构放在存储矩阵条目的内存中!所以当你更新矩阵时,你可能会改变宽度。编译器必须防范这些漏洞,因此它无法进行 'obviously should' 能够进行的优化。有时,一种情况下的漏洞很可能是程序员在另一种情况下的明显意图。一般来说,这类循环漏洞往往到处都是,编译器比人类更善于发现这些漏洞。