Monday, December 16, 2024

Inlined vs Non-inlined C++

In various programming languages, it is possible to inline code. For example, with C++, one can use design patterns or keywords like extern, static, or inline to suggest to the compiler that code should be inlined. Non-inlined code can generate extra, unnecessary calls to the global offset table or procedure linkage table.

Consider the following non-inlined code:

int g;

int foo() { 
    return g; 
    }

int bar() {
    g = 1;
    foo(); 
    return g;
}

If we compile the code like so with gcc -O3 -Wall -fPIC -m32, we can observe in the assembly that indeed, this code was not inlined. There are extra calls to the PLT.

foo():
        call    __x86.get_pc_thunk.ax
        add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        mov     eax, DWORD PTR g@GOT[eax]
        mov     eax, DWORD PTR [eax]
        ret
bar():
        push    esi
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 4
        mov     esi, DWORD PTR g@GOT[ebx]
        mov     DWORD PTR [esi], 1
        call    foo()@PLT
        mov     eax, DWORD PTR [esi]
        add     esp, 4
        pop     ebx
        pop     esi
        ret
g:
        .zero   4
__x86.get_pc_thunk.ax:
        mov     eax, DWORD PTR [esp]
        ret
__x86.get_pc_thunk.bx:
        mov     ebx, DWORD PTR [esp]
        ret
        

Now consider the use of the inline keyword.

int g;

inline int foo() { 
    return g; 
    }

int bar() {
    g = 1;
    foo(); 
    return g;
}

The aforementioned code vastly reduces the amount of instructions generated by GCC—the function foo has essentially been merged into bar. If this were a small program that made repeated calls to these functions, this optimization could reduce overhead and increase performance.

bar():
        call    __x86.get_pc_thunk.ax
        add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        mov     eax, DWORD PTR g@GOT[eax]
        mov     DWORD PTR [eax], 1
        mov     eax, 1
        ret
g:
        .zero   4
__x86.get_pc_thunk.ax:
        mov     eax, DWORD PTR [esp]
        ret
        

The inline keyword is a hint to the compiler. The compiler may not always follow such suggestions—it is context dependent. Other functions may be inlined and optimized by default even without such hints. But if you're nudging the compiler with inline suggestions, caveats may apply[1][2][3].

No comments:

Post a Comment