【问题标题】:Helping the compiler optimize function pointers帮助编译器优化函数指针
【发布时间】:2012-11-04 10:27:24
【问题描述】:

在 C 中实现类似 OO 的代码封装和多态性的常用方法是将不透明指针返回到包含一些函数指针的结构。这是一种非常常见的模式,例如在 Linux 内核中。

使用函数指针而不是函数调用会引入开销,由于缓存,该开销几乎可以忽略不计,正如其他问题中已经讨论的那样。

但是,使用 GCC (>4.6) 的新 -fwhole-program 和 -flto 优化选项,情况发生了变化。

libPointers.c

#include <stdlib.h>
#include "libPointers.h"

void do_work(struct worker *wrk, const int i) 
{
        wrk->datum += i;
}

struct worker *libPointers_init(const int startDatum)
{
        struct worker *wrk = malloc (sizeof (struct worker));

        *wrk = (struct worker) {
                .do_work = do_work,
                .datum = startDatum
        };

        return wrk;
}

libPointers.h

#ifndef __LIBPOINTERS_H__
#define __LIBPOINTERS_H__


struct worker {
        int datum;

        void (*do_work)(struct worker *, int i);
};

extern void do_work (struct worker *elab, const int i);

struct worker *libPointers_init(const int startDatum);


#endif //__LIBPOINTERS_H__

testPointers.c

#include <stdio.h>
#include "libPointers.h"


int main (void)
{
        unsigned long i;
        struct worker *wrk;

        wrk = libPointers_init(56);

        for (i = 0; i < 1e10; i++) {
#ifdef USE_POINTERS
                wrk->do_work(wrk,i);
#else
                do_work(wrk,i);
#endif
        }

        printf ("%d\n", wrk->datum);
}

使用 -O3 编译,但没有 -flto -fwhole-program 标志,无论 USE_POINTERS 是否为#defined,testPointers 在我的机器上执行大约需要 25 秒。

如果我打开 -flto -fwhole-program 标志,使用 USE_POINTERS #defined testPointers 大约需要 25 秒,但如果使用函数调用则大约需要 14 秒。

这是完全预期的行为,因为我知道编译器将内联并优化循环中的函数。但是,我想知道是否有办法帮助编译器告诉它函数指针是常量,从而允许它也优化这种情况。

对于那些使用 cmake 的人,这是我的编译方式

CMakeLists.txt

set (CMAKE_C_FLAGS "-O3 -fwhole-program -flto")
#set (CMAKE_C_FLAGS "-O3")
add_executable(testPointers
        libPointers.c
        testPointers.c
        )

【问题讨论】:

  • 如果在循环外的局部函数指针中捕获wrk-&gt;do_work 的值,然后在循环内使用该局部变量会怎样?
  • 你怎么敢问一个有意义的好问题? :P(+1,这很有趣。)
  • 一个问题是do_work 做的工作非常少。如果它确实做了一些重要的事情,呼叫速度的差异将更难衡量(因此不是很显着)。
  • 我明白,但 getter/setter 函数非常常见,而且工作量更少。通过整个程序优化,我们可以在最终二进制文件中调解对数据的访问和对这些简单操作的全面优化。
  • @GregHewgill 我这样改了,不幸的是没有区别 int main (void) { unsigned long i;结构工人 *wrk; wrk = libPointers_init(56); #ifdef USE_POINTERS void (*const f_do_work)(struct worker *, int i) = wrk->do_work; #endif for (i = 0; i datum); }

标签: c optimization gcc linux-kernel function-pointers


【解决方案1】:

如果你在循环中调用函数指针,你可以在函数指针内移动循环。

【讨论】:

    【解决方案2】:

    编译器不能内联函数,除非它可以确定只调用该函数的一个可能版本。通过指针调用,情况并非显而易见。编译器仍然有可能弄清楚它,因为如果您按照代码进行操作,则指针只能采用一个可能的值;但是,这将超出我对编译器的预期。

    【讨论】:

    • 是的,这就是我所说的“这完全是预期的行为”。我想知道是否存在 const 属性的某种组合,它会告诉编译器该函数不会改变,如“纯”、“常量”等......这至少有助于常见的“非虚方法”案例。
    • 另一个常见的关键字是restrict 关键字,它有类似的约定:请放心,您可以以某种方式处理指针。很高兴地说“这个回调将始终指向这个函数”
    • 如果您作为程序员,知道在某些情况下会始终调用特定函数,那么您可以直接调用该函数而不是使用指针。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-02
    相关资源
    最近更新 更多