如何提高内联函数的效率?

5

我对代码进行了剖析,发现一个内联函数占用了大约8%的样本。该函数是将矩阵下标转换为索引的函数。它很像Matlab中的sub2ind函数。

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

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

有什么方法可以改进吗?或明确让编译器执行内联扩展?


3
检查装配件,寻找函数调用。 - user3528438
1
汇编验证是一个很好的方法,但根据我的经验使用分析工具时,如果函数符号在分析工具中显示出来,那么它就没有被内联,因为内联函数会使热点在调用方而不是函数本身显示出来。如果汇编验证显示出一个函数调用,则建议确保该函数在头文件中可见(链接器可以内联代码,但我在使用旧编译器时取得更好的成功经验时,编译期间可以将函数内联 -- 尽管我不幸地必须在工作中使用旧编译器,所以这个建议对于新编译器可能并不适用)。 - user4842163
另一个尝试的方法是验证构建设置,确保优化已正确打开。我会考虑像__forceinline这样的东西作为最后的手段。 - user4842163
2个回答

5

你确定编译时开启了优化吗?某些编译器有一个属性可以强制内联,即使编译器不想这样做:请参见此问题

但它可能已经这样做了;您可以尝试让编译器输出汇编代码,并通过这种方式确认。

索引计算可能是您时间的重要部分--例如,如果您的算法是从矩阵中读取、进行少量计算,然后写回,那么索引计算确实是您计算时间的重要部分。

或者,您以一种编译器无法证明width在循环中始终保持不变的方式编写了代码,因此每次都必须重新从内存中读取它,以确保。尝试将width复制到本地变量并在内部循环中使用它。

现在,您已经说过这占用了您8%的时间--这意味着您可能无法获得超过8%的性能改进,而且可能更少。如果这真的很值得,那么该做的事情可能是根本改变如何遍历数组。

例如:

  • 如果您倾向于以线性方式访问矩阵,则可以编写某种二维迭代器类,您可以向上、向下、向左或向右前进,它将在任何地方使用加法而不是乘法
  • 同样的事情,但编写一个“索引”类,仅保存数字而不是假装是指针
  • 如果width是编译时常量,则可以将其明确设置为模板参数,您的编译器可能能够使用更聪明的方法进行乘法。

*:您可能会做一些愚蠢的事情,比如将矩阵的数据结构放在存储矩阵条目的内存中!因此,当您更新矩阵时,可能会更改宽度。编译器必须防范这些漏洞,因此无法进行“明显应该”能够进行的优化。有时候,在另一种情况下,漏洞的一种可能是程序员的明显意图。总的来说,这些类型的漏洞往往无处不在,编译器比人更擅长发现这些漏洞。


请您能解释一下您最后一个观点吗?在我的代码中,矩阵的大小(即width)保持不变,但是sub_heightsub_width在循环中会发生变化。 - ZHOU
但是编译器是否“知道”它是常量呢?有一些非常简单的方法可以打开漏洞而不知道。例如,如果您总是调用sub2ind(x, y, mymatrix.width());,并且编译器没有绝对证明存储宽度的内存位置没有被更改的方法(例如,因为没有任何东西否认您决定从存储宽度的内存地址开始存储矩阵内容,所以mymatrix[0] = 1;会改变宽度),那么它必须编写检查宽度更改的代码。 - user1084944

1
正如@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    $32, %rsp
    movl    $1, -16(%rbp)
    movl    $2, -12(%rbp)
    movl    $3, -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    $1, -24(%rsp)
    movl    $2, -20(%rsp)
    movq    -24(%rsp), %rax
    movl    $3, -16(%rsp)
    movq    %rax, -40(%rsp)
    movl    $3, -32(%rsp)
    movl    -32(%rsp), %eax
    movl    -36(%rsp), %edx
    movl    -40(%rsp), %ecx
    imull   %ecx, %eax
    addl    %edx, %eax
    ret
    .cfi_endproc

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


尝试使用用户输入而不是硬编码参数来编译程序可能更合理。我认为函数调用仍将被消除,但最好进行验证。 - jma127
@jma127 是的,但这只是一个示例,用来演示如何检查函数是否被实际调用。 - vitaut
当然可以,但这只是预先计算值的一个例子,而不是内联 -- 我认为意图是验证任意输入的内联。 - jma127
@jma127 好的,我将输入设置为可变以避免计算被优化掉。 - vitaut

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接