9

StackOverflowers 様

Microsoft Visual Studio C++ 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 フラグ) およびインライン関数展開の「任意」でコンパイルしました。(/Ob2 フラグ)

constであるため、コンパイラがこの呼び出しを特別にインライン化しないのはなぜだろうと思っていました。インライン化されていない理由と、コンパイラーをインライン化できるかどうかを知っている人はいますか?

キリスト教徒

編集:私は現在いくつかのテストを実行していますが、MSVC は次の場合にも関数ポインターをインライン化できません。

- const ポインターをクラスの外に移動し、グローバルにします。

- const ポインターをクラスの外に移動し、main でローカルにします。

-ポインターを非定数にして、ローカルに移動します。

-戻り値の型を無効にして、パラメーターを指定しない場合

私は、Microsoft Visual Studio が関数ポインターをまったくインライン化できないと信じ始めています...

4

4 に答える 4

2

問題は、コンパイラがあらゆる機会に行うインライン化ではありません。問題は、ポインター変数が実際にはコンパイル時の定数であることを Visual C++ が認識していないように見えることです。

テストケース:

// 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;
}

関数は複数のコンパイル単位に分割され、インライン化をより適切に制御できます。show_int具体的には、アセンブリ コードが乱雑になるため、インライン化は望ましくありません。

問題の最初の気配は、有効なコード (コメント行) が Visual C++ によって拒否されることです。 G++ では問題ありませんが、Visual C++ では「コンパイル時の定数式が予期される」と文句を言われます。これは、実際には、将来のすべての動作の優れた予測因子です。

最適化が有効で、通常のコンパイル セマンティクス (モジュール間のインライン化なし) を使用すると、コンパイラは次を生成します。

_main   PROC                        ; COMDAT

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

    push    2
    push    1
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    4
    push    3
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    6
    push    5
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; 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(ソース コードで関数ポインターが使用されている場合でも)。

function_pointer_resolution.cpp と sum.cpp を でコンパイルし/GLてリンクすることでインライン化を有効にする/LTCGと、コンパイラがすべての直接呼び出しをインライン化することがわかります。間接呼び出しはそのままです。

_main   PROC                        ; COMDAT

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

    push    3
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    7
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    11                  ; 0000000bH
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

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

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; 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);

すべてのテストは、x64 でホストされている x86 用にコンパイルされた Visual C++ 2010 Service Pack 1 で実行されます。

Microsoft (R) 32 ビット C/C++ 最適化コンパイラ バージョン 16.00.40219.01 for 80x86

于 2013-05-09T22:49:49.157 に答える
0

You can try __forceinline. Nobody is going to be able to tell you exactly why it isn't inlined. Common sense says to me that it should be, however. /O2 should favor code speed over code size (inlining)... Strange.

于 2013-05-09T16:33:40.233 に答える
0

これは本当の答えではありませんが、「おそらく回避策」の 1 つです。Microsoft の STL は、ラムダは f ptrs よりも簡単にインライン化できると述べていたので、それを試すことができます。

トリビアとして、Bjarne は、qsort は関数 ptr を取るため、qsort よりも sort の方が高速であるとよく述べていますが、他の人が指摘しているように、gcc は問題なくインライン化できると述べています...だから、Bjarne は gcc を試してみるべきでしょう :P

于 2014-02-24T18:04:31.120 に答える