Visual С++ ~ Не встраивание простых указателей на указатели функции const

Дорогой StackOverflowers,

У меня есть простая часть кода, которую я компилирую на Microsoft Visual Studio С++ 2012:

int add(int x, int y)
{
    return x + y;
}

typedef int (*func_t)(int, int);

class A
{
public:
    const static func_t FP;
};

const func_t A::FP = &add;

int main()
{
 int x = 3;
 int y = 2;
 int z = A::FP(x, y);
 return 0;
}

Компилятор генерирует следующий код:

int main()
{
000000013FBA2430  sub         rsp,28h  
int x = 3;
int y = 2;
int z = A::FP(x, y);
000000013FBA2434  mov         edx,2  
000000013FBA2439  lea         ecx,[rdx+1]  
000000013FBA243C  call        qword ptr [A::FP (013FBA45C0h)]  
return 0;
000000013FBA2442  xor         eax,eax
}

Я скомпилировал это на "Полная оптимизация" (/Obx flag) и "Любой подходящий" для расширения функции Inline. (/Ob2 флаг)

Мне было интересно, почему компилятор не встраивает этот вызов с момента его создания. У кого-нибудь из вас есть идея, почему он не встроен и если можно сделать его компилятором?

Christian

EDIT: теперь я запускаю несколько тестов, и MSVC не может встроить указатели на функции, когда:

- Переместите указатель const из класса и сделайте его глобальным.

- Переместите указатель const из класса и сделайте его локальным в основном.

- я создаю указатель не const и перемещаю его локально.

-Когда я создаю возвращаемый тип void и не даю ему никаких параметров

Я добрый, полагаю, Microsoft Visual Studio не может встроить указатели на объекты...

Ответ 1

Проблема заключается не в встраивании, которое компилятор делает при каждой возможности. Проблема в том, что Visual С++, похоже, не понимает, что переменная указателя на самом деле является константой времени компиляции.

Test-случай:

// function_pointer_resolution.cpp : Defines the entry point for the console application.
//

extern void show_int( int );

extern "C" typedef int binary_int_func( int, int );

extern "C" binary_int_func sum;
extern "C" binary_int_func* const sum_ptr = sum;

inline int call( binary_int_func* binary, int a, int b ) { return (*binary)(a, b); }

template< binary_int_func* binary >
inline int callt( int a, int b ) { return (*binary)(a, b); }

int main( void )
{
    show_int( sum(1, 2) );
    show_int( call(&sum, 3, 4) );
    show_int( callt<&sum>(5, 6) );
    show_int( (*sum_ptr)(1, 7) );
    show_int( call(sum_ptr, 3, 8) );
//  show_int( callt<sum_ptr>(5, 9) );
    return 0;
}

// sum.cpp
extern "C" int sum( int x, int y )
{
    return x + y;
}

// show_int.cpp
#include <iostream>

void show_int( int n )
{
    std::cout << n << std::endl;
}

Функции разделяются на несколько единиц компиляции, чтобы обеспечить лучший контроль над inlining. В частности, я не хочу show_int inlined, так как он делает код сборки беспорядочным.

Первым неприятным случаем является то, что действительный код (прокомментированная строка) отклоняется Visual С++. У g++ нет проблем с ним, но Visual С++ жалуется на "ожидаемое выражение постоянной компиляции". Это на самом деле хороший предиктор всего будущего поведения.

С включенной оптимизацией и нормальной семантикой компиляции (без кросс-модульной вставки) компилятор генерирует:

_main   PROC                        ; COMDAT

; 18   :    show_int( sum(1, 2) );

    push    2
    push    1
    call    _sum
    push    eax
    call    [email protected]@[email protected]           ; show_int

; 19   :    show_int( call(&sum, 3, 4) );

    push    4
    push    3
    call    _sum
    push    eax
    call    [email protected]@[email protected]           ; show_int

; 20   :    show_int( callt<&sum>(5, 6) );

    push    6
    push    5
    call    _sum
    push    eax
    call    [email protected]@[email protected]           ; show_int

; 21   :    show_int( (*sum_ptr)(1, 7) );

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    [email protected]@[email protected]           ; show_int

; 22   :    show_int( call(sum_ptr, 3, 8) );

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    [email protected]@[email protected]           ; show_int
    add esp, 60                 ; 0000003cH

; 23   :    //show_int( callt<sum_ptr>(5, 9) );
; 24   :    return 0;

    xor eax, eax

; 25   : }

    ret 0
_main   ENDP

Уже существует огромная разница между использованием sum_ptr и не использованием sum_ptr. Выражения с использованием sum_ptr генерируют вызов косвенной функции call DWORD PTR _sum_ptr, в то время как все остальные операторы генерируют прямой вызов функции call _sum, даже если исходный код использовал указатель на функцию.

Если теперь включить встраивание путем компиляции функций_pinter_resolution.cpp и sum.cpp с помощью /GL и связывания с /LTCG, мы обнаружим, что компилятор строит все прямые вызовы. Косвенные вызовы остаются как есть.

_main   PROC                        ; COMDAT

; 18   :    show_int( sum(1, 2) );

    push    3
    call    [email protected]@[email protected]           ; show_int

; 19   :    show_int( call(&sum, 3, 4) );

    push    7
    call    [email protected]@[email protected]           ; show_int

; 20   :    show_int( callt<&sum>(5, 6) );

    push    11                  ; 0000000bH
    call    [email protected]@[email protected]           ; show_int

; 21   :    show_int( (*sum_ptr)(1, 7) );

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    [email protected]@[email protected]           ; show_int

; 22   :    show_int( call(sum_ptr, 3, 8) );

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    [email protected]@[email protected]           ; show_int
    add esp, 36                 ; 00000024H

; 23   :    //show_int( callt<sum_ptr>(5, 9) );
; 24   :    return 0;

    xor eax, eax

; 25   : }

    ret 0
_main   ENDP

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

call(&sum, 3, 4);

но это не так:

(*sum_ptr)(1, 7);

Все тесты выполняются с Visual С++ 2010 с пакетом обновления 1 (SP1), компиляция для x86, размещенная на x64.

Microsoft (R) 32-разрядная версия оптимизатора C/С++ версии 16.00.40219.01 для 80x86

Ответ 2

Вы можете попробовать __forceinline. Никто не сможет точно сказать, почему это не указано. Здравый смысл говорит мне, что это должно быть, однако. /O 2 должен поддерживать скорость кода по размеру кода (вставка)... Странно.

Ответ 3

Я думаю, что вы правы в этом заключении: "... не может встроить указатели на все".

Этот очень простой пример также нарушает оптимизацию:

static inline
int add(int x, int y)
{
    return x + y;
}

int main()
{
    int x = 3;
    int y = 2;
    auto q = add;
    int z = q(x, y);
    return z;
}

Ваш образец еще сложнее для компилятора, поэтому это не удивительно.

Ответ 4

Это не реальный ответ, а "возможно, обходной": STL от Microsoft однажды упомянул, что lambdas более легко inlineable, чем f ptrs, чтобы вы могли попробовать это.

Как мелочи Бьярне часто упоминает, что сортировка быстрее, чем qsort, потому что qsort принимает функцию ptr, но, как и другие люди, отметили, что gcc не имеет проблем с их вложением... поэтому, возможно, Bjarne должен попробовать gcc: P