Подтвердить что ты не робот

Помогая компилятору оптимизировать указатели функций

Общим способом реализации инкапсуляции и полиморфизма OO-подобных объектов в C является возврат непрозрачных указателей к структуре, содержащей некоторые указатели на функции. Это очень частая модель, например, в ядре Linux.

Использование указателей функций вместо вызовов функций приводит к накладным расходам, которые в основном игнорируются из-за кэширования, как уже обсуждалось в других вопросах.

Однако с новыми параметрами оптимизации -fwole-program и -flto для GCC ( > 4.6) все меняется.

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, выполнение testPointers занимает около 25 секунд на моей машине, независимо от того, является ли USE_POINTERS #defined или нет.

Если я включаю флаги -flto -fwhole-program, testPointers занимает около 25 с USE_POINTERS #defined, но около 14 секунд, если используется вызов функции.

Это полностью ожидаемое поведение, так как я понимаю, что компилятор будет встроить и оптимизировать функцию в цикле. Интересно, однако, если есть способ помочь компилятору сказать ему, что указатель функции является постоянным и поэтому позволяет ему также оптимизировать этот случай.

Для тех, кто использует cmake, вот как я скомпилировал его

CMakeLists.txt

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

Ответ 1

Компилятор не может встроить функцию, если только он не может определить, что будет вызываться только одна возможная версия функции. Вызывая через указатель, нетривиально очевидно, что это так. Возможно, компилятор может понять, что, если вы будете следовать этому коде, возможно только одно возможное значение, которое может принять указатель; однако это было бы выше и выше того, что я ожидал бы от компилятора.

Ответ 2

Если вы вызываете указатель функции в цикле, вы можете переместить цикл внутри указателя функции.